ryugi-artifacts 1.15.1 → 1.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (152) hide show
  1. package/dist/cli.cjs +48 -15
  2. package/dist/server.cjs +572 -252
  3. package/dist/viewer/CloudDocument-CM9pTTjC.js +1 -0
  4. package/dist/viewer/CloudLiveDocument-DGNdJWbj.js +1 -0
  5. package/dist/viewer/{DocumentLayout-BDQ2bRgn.js → DocumentLayout-BMPgq8pc.js} +157 -157
  6. package/dist/viewer/DocumentPage-g1CqDkue.js +1 -0
  7. package/dist/viewer/{abnfDiagram-VCTEODGH-DXcz0DY8.js → abnfDiagram-VCTEODGH-R_qwpGkI.js} +1 -1
  8. package/dist/viewer/{ar-SA-G6X2FPQ2-DaFICdeh.js → ar-SA-G6X2FPQ2-CQRrwr6s.js} +1 -1
  9. package/dist/viewer/{architectureDiagram-5GKGNRK7-7HwBC9Wz.js → architectureDiagram-5GKGNRK7-B-nxVlz4.js} +1 -1
  10. package/dist/viewer/{az-AZ-76LH7QW2-ro71YLew.js → az-AZ-76LH7QW2-BSmwjSSb.js} +1 -1
  11. package/dist/viewer/{bg-BG-XCXSNQG7-BBql1i2c.js → bg-BG-XCXSNQG7--cdnQcg0.js} +1 -1
  12. package/dist/viewer/{blockDiagram-I7D4REHJ-DN6_CCIZ.js → blockDiagram-I7D4REHJ-C0OuOJoH.js} +1 -1
  13. package/dist/viewer/{bn-BD-2XOGV67Q-D0nEk_sk.js → bn-BD-2XOGV67Q-1ryNaFLF.js} +1 -1
  14. package/dist/viewer/{c4Diagram-7LVT6UL2-BiKuFZ_0.js → c4Diagram-7LVT6UL2-DZkEOB1y.js} +1 -1
  15. package/dist/viewer/{ca-ES-6MX7JW3Y-BzXg8v5p.js → ca-ES-6MX7JW3Y-VdWP3ryz.js} +1 -1
  16. package/dist/viewer/{chunk-4HAMMTFA-D8wduydj.js → chunk-4HAMMTFA-VNoPgKvb.js} +1 -1
  17. package/dist/viewer/{chunk-EIO257PC-KVQLe9-1.js → chunk-EIO257PC-CZIhLO_W.js} +1 -1
  18. package/dist/viewer/{chunk-GMAD6QVW-BPxTHMTi.js → chunk-GMAD6QVW-CqoLeHeY.js} +1 -1
  19. package/dist/viewer/{chunk-GVQU2GXP-C5_2EAAn.js → chunk-GVQU2GXP-CQg0Jmx3.js} +1 -1
  20. package/dist/viewer/{chunk-IMKFNOWR-CoST25Zy.js → chunk-IMKFNOWR-DXIxj3u0.js} +1 -1
  21. package/dist/viewer/{chunk-L3NEJ4N5-DqtaXAcI.js → chunk-L3NEJ4N5-x6DZXmqs.js} +1 -1
  22. package/dist/viewer/{chunk-OSK3NFVY-DacvL7tA.js → chunk-OSK3NFVY-CPGpRAxP.js} +1 -1
  23. package/dist/viewer/{chunk-PWAF6VOD-Cxj_hOdt.js → chunk-PWAF6VOD-B4vVusV7.js} +1 -1
  24. package/dist/viewer/{chunk-SHT3W25Y-D9pKJ6Au.js → chunk-SHT3W25Y-D1zbaWDs.js} +1 -1
  25. package/dist/viewer/{chunk-SVP7TREG-svneeNVA.js → chunk-SVP7TREG-BWLqA0kk.js} +1 -1
  26. package/dist/viewer/{chunk-TICWLB2K-xfr9ROyW.js → chunk-TICWLB2K-D8K_a2Dt.js} +1 -1
  27. package/dist/viewer/{classDiagram-ZZMXUADV-lu71gUXJ.js → classDiagram-ZZMXUADV-Ck-ZG83x.js} +1 -1
  28. package/dist/viewer/{classDiagram-v2-VYDZK3BY-lu71gUXJ.js → classDiagram-v2-VYDZK3BY-Ck-ZG83x.js} +1 -1
  29. package/dist/viewer/{cs-CZ-2BRQDIVT-bwZx9UIu.js → cs-CZ-2BRQDIVT-CgQlVQeV.js} +1 -1
  30. package/dist/viewer/{cynefinDiagram-5FMLGOSQ-DlNaJbj_.js → cynefinDiagram-5FMLGOSQ-Dm5JUsZt.js} +1 -1
  31. package/dist/viewer/{da-DK-5WZEPLOC-BApDVcwE.js → da-DK-5WZEPLOC-BgjfHXZN.js} +1 -1
  32. package/dist/viewer/{dagre-GXQ25YYZ-DWu2Ycc5.js → dagre-GXQ25YYZ-C4a96VLk.js} +1 -1
  33. package/dist/viewer/{de-DE-XR44H4JA-COcHc6Zt.js → de-DE-XR44H4JA-DX-3RcAq.js} +1 -1
  34. package/dist/viewer/{diagram-S7CK7UJ4-D8VO7YKc.js → diagram-S7CK7UJ4-PaaBHmBV.js} +1 -1
  35. package/dist/viewer/{diagram-UQ7AKVKN-D5OcQNY9.js → diagram-UQ7AKVKN-CFIzU-ol.js} +1 -1
  36. package/dist/viewer/{diagram-VX7I27RA-DRHmiAWI.js → diagram-VX7I27RA-CbcVVLx5.js} +1 -1
  37. package/dist/viewer/{diagram-Z3DM3KII-ClXzWLem.js → diagram-Z3DM3KII-2bnKxhw3.js} +1 -1
  38. package/dist/viewer/{dist-BuKe8NdN.js → dist-BB8Cm8p1.js} +1 -1
  39. package/dist/viewer/dist-BGDD3Qfs.js +1 -0
  40. package/dist/viewer/{dist-M7Lz5R2v.js → dist-BNUmLPG3.js} +1 -1
  41. package/dist/viewer/{dist-TX5rYYGW.js → dist-ByHcWAsz.js} +4 -4
  42. package/dist/viewer/{dist-G2JHMLgK.js → dist-C84cxxJs.js} +1 -1
  43. package/dist/viewer/{dist-q1jFGEbP.js → dist-C85UfTX9.js} +1 -1
  44. package/dist/viewer/{dist-BKtxxS1a.js → dist-CAYAG4sb.js} +1 -1
  45. package/dist/viewer/{dist-DLh5uREE.js → dist-CHo5vjEE.js} +1 -1
  46. package/dist/viewer/{dist-CFkCXjQP.js → dist-CPsNYKE1.js} +1 -1
  47. package/dist/viewer/dist-CbM-kcUd.js +6 -0
  48. package/dist/viewer/{dist-C_NwF-ep.js → dist-Cifjyebz.js} +9 -9
  49. package/dist/viewer/{dist-Bgijnk4D.js → dist-Cmmhn-X1.js} +1 -1
  50. package/dist/viewer/dist-DA1ZUPA2.js +1 -0
  51. package/dist/viewer/{dist-DlANbAUX.js → dist-DOIJJqiU.js} +1 -1
  52. package/dist/viewer/{dist-CByD4sdq.js → dist-DYmYmDEo.js} +1 -1
  53. package/dist/viewer/{dist-rgdCugHI.js → dist-DdnIT8Pk.js} +1 -1
  54. package/dist/viewer/{dist-mDWB-P3S.js → dist-DfU4jTpk.js} +1 -1
  55. package/dist/viewer/{dist-DB3o8dUU.js → dist-DuNQu2Qs.js} +1 -1
  56. package/dist/viewer/dist-DyvqUJBr.js +1 -0
  57. package/dist/viewer/{dist-CV7QdHTq.js → dist-Dz0HV18p.js} +1 -1
  58. package/dist/viewer/{dist-DzBPEHnz.js → dist-KZU_4K7q.js} +5 -5
  59. package/dist/viewer/dist-LjHj6cw9.js +9 -0
  60. package/dist/viewer/{dist-a8BuHhYq.js → dist-Saw8jlKm.js} +1 -1
  61. package/dist/viewer/{dist-DfWxOyg4.js → dist-all60C8n.js} +2 -2
  62. package/dist/viewer/{ebnfDiagram-PWID7BFC-BOzsyHEk.js → ebnfDiagram-PWID7BFC-BElETRrd.js} +1 -1
  63. package/dist/viewer/{el-GR-BZB4AONW-h1eLQlP7.js → el-GR-BZB4AONW-CCgkhpuC.js} +1 -1
  64. package/dist/viewer/{en-B4ZKOASM-EdleGu9D.js → en-B4ZKOASM-CkNYcd6h.js} +1 -1
  65. package/dist/viewer/{erDiagram-RLTQ6QDP-BF_tpcM3.js → erDiagram-RLTQ6QDP-BvirYYn6.js} +1 -1
  66. package/dist/viewer/{es-ES-U4NZUMDT-DeaeHEK9.js → es-ES-U4NZUMDT-CHGDrG6V.js} +1 -1
  67. package/dist/viewer/{eu-ES-A7QVB2H4-BzoRNYbw.js → eu-ES-A7QVB2H4-CJvnBnSO.js} +1 -1
  68. package/dist/viewer/{fa-IR-HGAKTJCU-fXYoNuWo.js → fa-IR-HGAKTJCU-DOhVqJIY.js} +1 -1
  69. package/dist/viewer/{fi-FI-Z5N7JZ37-CTVEZXqM.js → fi-FI-Z5N7JZ37-KtjD042q.js} +1 -1
  70. package/dist/viewer/flowDiagram-HODETNUW-B9qTl1Fa.js +1 -0
  71. package/dist/viewer/{fr-FR-RHASNOE6-BizkpgaG.js → fr-FR-RHASNOE6-Fhuh4xKm.js} +1 -1
  72. package/dist/viewer/ganttDiagram-EL5Y4UJY-vMMpYRDJ.js +292 -0
  73. package/dist/viewer/{gl-ES-HMX3MZ6V-rsfHb2R9.js → gl-ES-HMX3MZ6V-GaQgPV8F.js} +1 -1
  74. package/dist/viewer/{he-IL-6SHJWFNN-BmWEwAWC.js → he-IL-6SHJWFNN-DHPr7Ged.js} +1 -1
  75. package/dist/viewer/{hi-IN-IWLTKZ5I-gzYZTFIt.js → hi-IN-IWLTKZ5I-d4wZNnj5.js} +1 -1
  76. package/dist/viewer/{hu-HU-A5ZG7DT2-DqXGHJ4g.js → hu-HU-A5ZG7DT2-qpHSyZvF.js} +1 -1
  77. package/dist/viewer/{id-ID-SAP4L64H-CVbSVAc3.js → id-ID-SAP4L64H-CPrjJiXr.js} +1 -1
  78. package/dist/viewer/{image-GAAHSSAO-B0g7YyLf.js → image-GAAHSSAO-xfEH5EVg.js} +1 -1
  79. package/dist/viewer/index.html +1 -1
  80. package/dist/viewer/index.js +6 -6
  81. package/dist/viewer/index2.css +171 -18
  82. package/dist/viewer/{infoDiagram-27XIBGKW-DOxIYIRL.js → infoDiagram-27XIBGKW-B0yRhU0B.js} +1 -1
  83. package/dist/viewer/{ishikawaDiagram-5VMMS53U-D-U6Fi8F.js → ishikawaDiagram-5VMMS53U-ZFUPUZ0x.js} +1 -1
  84. package/dist/viewer/{it-IT-JPQ66NNP-6PNiI36L.js → it-IT-JPQ66NNP-BnOpgYU3.js} +1 -1
  85. package/dist/viewer/{ja-JP-DBVTYXUO-D3SJtNp5.js → ja-JP-DBVTYXUO-Fma_1_jf.js} +1 -1
  86. package/dist/viewer/{journeyDiagram-3NMN7TZE-8Mz0Nae3.js → journeyDiagram-3NMN7TZE-GaSIHccq.js} +1 -1
  87. package/dist/viewer/{kaa-6HZHGXH3-Bz3WA5yL.js → kaa-6HZHGXH3-Bgd1Vsyj.js} +1 -1
  88. package/dist/viewer/{kab-KAB-ZGHBKWFO-W2PtzMEV.js → kab-KAB-ZGHBKWFO-97ekuQIQ.js} +1 -1
  89. package/dist/viewer/{kanban-definition-UXKFOSKX-BP8HwPzL.js → kanban-definition-UXKFOSKX-BfckuyD5.js} +1 -1
  90. package/dist/viewer/{kk-KZ-P5N5QNE5-Dc1i0Bup.js → kk-KZ-P5N5QNE5-Bxv4UjqE.js} +1 -1
  91. package/dist/viewer/{km-KH-HSX4SM5Z-C88_w0rR.js → km-KH-HSX4SM5Z-8ccFfBZa.js} +1 -1
  92. package/dist/viewer/{ko-KR-MTYHY66A-DWOt-6As.js → ko-KR-MTYHY66A-D-a1HV8D.js} +1 -1
  93. package/dist/viewer/{ku-TR-6OUDTVRD-B7qeFzlo.js → ku-TR-6OUDTVRD-BsdyU65I.js} +1 -1
  94. package/dist/viewer/{lt-LT-XHIRWOB4-Dc9eguDu.js → lt-LT-XHIRWOB4-CIq7DtVN.js} +1 -1
  95. package/dist/viewer/{lv-LV-5QDEKY6T-D9CzWKMj.js → lv-LV-5QDEKY6T-B5B57KgN.js} +1 -1
  96. package/dist/viewer/{mindmap-definition-YA3MSWOX-Bikv3SLv.js → mindmap-definition-YA3MSWOX-x4MVcn_H.js} +1 -1
  97. package/dist/viewer/{mr-IN-CRQNXWMA-CtlsKIl9.js → mr-IN-CRQNXWMA-D1VztqX3.js} +1 -1
  98. package/dist/viewer/{my-MM-5M5IBNSE-CiyAGqHH.js → my-MM-5M5IBNSE-VGJv_B79.js} +1 -1
  99. package/dist/viewer/{nb-NO-T6EIAALU-l9uMRUdk.js → nb-NO-T6EIAALU-CW7CbXP6.js} +1 -1
  100. package/dist/viewer/{nl-NL-IS3SIHDZ-zja_zaqN.js → nl-NL-IS3SIHDZ-0fova8mt.js} +1 -1
  101. package/dist/viewer/{nn-NO-6E72VCQL-BiP5RLsf.js → nn-NO-6E72VCQL-Bl8c9odx.js} +1 -1
  102. package/dist/viewer/{oc-FR-POXYY2M6-CvKUzwab.js → oc-FR-POXYY2M6-Csx8D3AS.js} +1 -1
  103. package/dist/viewer/{pa-IN-N4M65BXN-DioFchQd.js → pa-IN-N4M65BXN-DrioQ4p2.js} +1 -1
  104. package/dist/viewer/{pegDiagram-XKGWAZYB-BSllNtPI.js → pegDiagram-XKGWAZYB-jgQ4-VU8.js} +1 -1
  105. package/dist/viewer/percentages-BXMCSKIN-DRVY_37B.js +1 -0
  106. package/dist/viewer/{pieDiagram-E7YTZNPT-b1-BsaA4.js → pieDiagram-E7YTZNPT-D4W5LODC.js} +3 -3
  107. package/dist/viewer/{pl-PL-T2D74RX3-Ci7dAnI8.js → pl-PL-T2D74RX3-BlG3Tr5U.js} +1 -1
  108. package/dist/viewer/{prod-CPZkgD7t.js → prod-DoKZsOaX.js} +9 -9
  109. package/dist/viewer/{pt-BR-5N22H2LF--VT7NYYE.js → pt-BR-5N22H2LF-BJ_dJDoY.js} +1 -1
  110. package/dist/viewer/{pt-PT-UZXXM6DQ-CWdbQilC.js → pt-PT-UZXXM6DQ-JuaprDPL.js} +1 -1
  111. package/dist/viewer/{quadrantDiagram-AXDQQJYC-D0pucWR0.js → quadrantDiagram-AXDQQJYC-Bjo_a-l0.js} +1 -1
  112. package/dist/viewer/{railroadDiagram-O6MQD6OU-DIPJ2PKr.js → railroadDiagram-O6MQD6OU-B0jgFLfP.js} +1 -1
  113. package/dist/viewer/{requirementDiagram-BXWQKSXE-DEGhiwJ9.js → requirementDiagram-BXWQKSXE-Dsdz_3bL.js} +1 -1
  114. package/dist/viewer/{ro-RO-JPDTUUEW-BK4wLUL7.js → ro-RO-JPDTUUEW-CPbEkuPw.js} +1 -1
  115. package/dist/viewer/{ru-RU-B4JR7IUQ-BDPQ8blU.js → ru-RU-B4JR7IUQ-DtGFv7Yn.js} +1 -1
  116. package/dist/viewer/{sequenceDiagram-WJ2MYXX4-D7sLhGYb.js → sequenceDiagram-WJ2MYXX4-BNHp74bi.js} +1 -1
  117. package/dist/viewer/{si-LK-N5RQ5JYF-D5DWsUAA.js → si-LK-N5RQ5JYF-Cw7PrSGM.js} +1 -1
  118. package/dist/viewer/{sk-SK-C5VTKIMK-Cia9j1Te.js → sk-SK-C5VTKIMK-BdEWLdAf.js} +1 -1
  119. package/dist/viewer/{sl-SI-NN7IZMDC-CV304k5j.js → sl-SI-NN7IZMDC-DWsrAmPB.js} +1 -1
  120. package/dist/viewer/{stateDiagram-D77RDMKH-BMNx9_M8.js → stateDiagram-D77RDMKH-bROf90_R.js} +1 -1
  121. package/dist/viewer/{stateDiagram-v2-MP3YSRHH-CPa9DZs4.js → stateDiagram-v2-MP3YSRHH-BY5bPCMJ.js} +1 -1
  122. package/dist/viewer/subset-shared.chunk-BStnlZMO.js +1 -0
  123. package/dist/viewer/{subset-worker.chunk-CHQzlaj3.js → subset-worker.chunk-ZX5ZauYv.js} +1 -1
  124. package/dist/viewer/{sv-SE-XGPEYMSR-CXdp-XZD.js → sv-SE-XGPEYMSR-CIhIa_l2.js} +1 -1
  125. package/dist/viewer/{swimlanes-42K2YHIH-BwUyxwi3.js → swimlanes-42K2YHIH-DWgSkFMr.js} +1 -1
  126. package/dist/viewer/{swimlanesDiagram-VR7AAH4N-BgxS36EG.js → swimlanesDiagram-VR7AAH4N-CJlfxAhN.js} +1 -1
  127. package/dist/viewer/{ta-IN-2NMHFXQM-CObY6q-8.js → ta-IN-2NMHFXQM-j2fOOXe2.js} +1 -1
  128. package/dist/viewer/{th-TH-HPSO5L25-CEOiELZN.js → th-TH-HPSO5L25-CkgXnjpf.js} +1 -1
  129. package/dist/viewer/{timeline-definition-24CTP7MA-BJ-7Fdfr.js → timeline-definition-24CTP7MA-rnUpi-7k.js} +1 -1
  130. package/dist/viewer/{tr-TR-DEFEU3FU-CPj5dFcB.js → tr-TR-DEFEU3FU-bgJR1iXd.js} +1 -1
  131. package/dist/viewer/{uk-UA-QMV73CPH-BkZyFeXP.js → uk-UA-QMV73CPH-q6uSQxvU.js} +1 -1
  132. package/dist/viewer/{useSandboxFrame-BpJ9uM86.js → useSandboxFrame-Dzr1V3HK.js} +1 -1
  133. package/dist/viewer/{vennDiagram-4TSXK5OY-DnFbjWDw.js → vennDiagram-4TSXK5OY-CeqdYGwH.js} +1 -1
  134. package/dist/viewer/{vi-VN-M7AON7JQ-CkKKOpBQ.js → vi-VN-M7AON7JQ-DTYGZATL.js} +1 -1
  135. package/dist/viewer/{wardleyDiagram-VM6X3IG4-CN_pm__V.js → wardleyDiagram-VM6X3IG4-C8cYdu7b.js} +1 -1
  136. package/dist/viewer/{xychartDiagram-S5SC5T6Z-C7XLywXm.js → xychartDiagram-S5SC5T6Z-Djgu6Zdz.js} +2 -2
  137. package/dist/viewer/{zh-CN-LNUGB5OW-BQcCqsDg.js → zh-CN-LNUGB5OW-BWV3WswS.js} +1 -1
  138. package/dist/viewer/{zh-HK-E62DVLB3-Bp-jK0zw.js → zh-HK-E62DVLB3-Bd20M5Rq.js} +1 -1
  139. package/dist/viewer/{zh-TW-RAJ6MFWO-KmFL88Bs.js → zh-TW-RAJ6MFWO-CVDAstAU.js} +1 -1
  140. package/package.json +1 -1
  141. package/dist/viewer/CloudDocument-CgM3kbiO.js +0 -1
  142. package/dist/viewer/CloudLiveDocument-CdN71jfH.js +0 -1
  143. package/dist/viewer/DocumentPage-BBzAFj4H.js +0 -1
  144. package/dist/viewer/dist-C2adJeNZ.js +0 -9
  145. package/dist/viewer/dist-D0Emc84u.js +0 -1
  146. package/dist/viewer/dist-DcGUli8O.js +0 -1
  147. package/dist/viewer/dist-VupXLYv6.js +0 -1
  148. package/dist/viewer/dist-_3NL_2LS.js +0 -6
  149. package/dist/viewer/flowDiagram-HODETNUW-DpY3t4b0.js +0 -1
  150. package/dist/viewer/ganttDiagram-EL5Y4UJY-DvL8TKGV.js +0 -292
  151. package/dist/viewer/percentages-BXMCSKIN-BRZnWvym.js +0 -1
  152. package/dist/viewer/subset-shared.chunk-ChUk9bHw.js +0 -1
package/dist/server.cjs CHANGED
@@ -5171,14 +5171,14 @@ var require_websocket_server = __commonJS({
5171
5171
 
5172
5172
  // src/server/main.ts
5173
5173
  var import_node_child_process2 = require("node:child_process");
5174
- var import_node_fs24 = __toESM(require("node:fs"), 1);
5174
+ var import_node_fs25 = __toESM(require("node:fs"), 1);
5175
5175
  var import_node_os = __toESM(require("node:os"), 1);
5176
- var import_node_path20 = __toESM(require("node:path"), 1);
5176
+ var import_node_path21 = __toESM(require("node:path"), 1);
5177
5177
 
5178
5178
  // src/server/app.ts
5179
- var import_node_fs22 = __toESM(require("node:fs"), 1);
5179
+ var import_node_fs23 = __toESM(require("node:fs"), 1);
5180
5180
  var import_node_http = __toESM(require("node:http"), 1);
5181
- var import_node_path19 = __toESM(require("node:path"), 1);
5181
+ var import_node_path20 = __toESM(require("node:path"), 1);
5182
5182
 
5183
5183
  // src/shared/types.ts
5184
5184
  var ARTIFACT_TYPES = ["artboard", "document", "html"];
@@ -5230,7 +5230,7 @@ function artifactKeyOf(relative) {
5230
5230
  if (!type || !id2 || !isArtifactType(type) || !isArtifactId(id2)) return null;
5231
5231
  return { type, id: id2 };
5232
5232
  }
5233
- function artifactSignature(dir) {
5233
+ function artifactSignature(dir, type) {
5234
5234
  let count = 0;
5235
5235
  let size = 0;
5236
5236
  let latest = 0;
@@ -5243,6 +5243,7 @@ function artifactSignature(dir) {
5243
5243
  }
5244
5244
  for (const entry of entries2) {
5245
5245
  if (entry.name.startsWith(".")) continue;
5246
+ if (type === "artboard" && current === dir && entry.name === "comments.json") continue;
5246
5247
  const full = import_node_path.default.join(current, entry.name);
5247
5248
  try {
5248
5249
  const stat = import_node_fs.default.statSync(full);
@@ -17727,12 +17728,19 @@ function stampBlockIds(state) {
17727
17728
  return { ...state, root: { ...state.root, children } };
17728
17729
  }
17729
17730
 
17730
- // src/server/document-comments.ts
17731
- var import_node_fs6 = __toESM(require("node:fs"), 1);
17731
+ // src/server/board-comments.ts
17732
+ var import_node_crypto2 = __toESM(require("node:crypto"), 1);
17733
+ var import_node_fs8 = __toESM(require("node:fs"), 1);
17734
+ var import_node_path7 = __toESM(require("node:path"), 1);
17732
17735
 
17733
17736
  // src/shared/comments.ts
17734
17737
  var emptyComments = () => ({ version: 1, threads: [] });
17735
17738
  var isObject = (value) => typeof value === "object" && value !== null;
17739
+ var documentAnchors = {
17740
+ read: (value) => isObject(value) && typeof value.blockId === "string" && value.blockId ? { blockId: value.blockId, quote: typeof value.quote === "string" ? value.quote : "" } : null,
17741
+ same: (a5, b6) => a5.blockId === b6.blockId && a5.quote === b6.quote,
17742
+ missing: "a new thread needs anchor.blockId; dropped it"
17743
+ };
17736
17744
  function parseComments(text2) {
17737
17745
  const raw = JSON.parse(text2);
17738
17746
  if (!isObject(raw)) throw new Error("comments.json must be an object");
@@ -17745,13 +17753,14 @@ function parseComments(text2) {
17745
17753
  return raw;
17746
17754
  }
17747
17755
  var isSavedMessage = (m11) => !!m11.id && (m11.author === "human" || m11.author === "agent") && typeof m11.text === "string" && typeof m11.at === "string";
17748
- function savedComments(incoming) {
17756
+ function savedComments(incoming, spec = documentAnchors) {
17749
17757
  const threads = [];
17750
17758
  for (const t7 of incoming.threads) {
17751
- if (!t7.id || !t7.anchor?.blockId || !t7.statusAt || t7.status !== "open" && t7.status !== "resolved") continue;
17759
+ const anchor = spec.read(t7.anchor);
17760
+ if (!t7.id || !anchor || !t7.statusAt || t7.status !== "open" && t7.status !== "resolved") continue;
17752
17761
  const messages = (t7.messages ?? []).filter(isSavedMessage);
17753
17762
  const statusBy = t7.statusBy === "human" || t7.statusBy === "agent" ? { statusBy: t7.statusBy } : {};
17754
- threads.push({ id: t7.id, anchor: { blockId: t7.anchor.blockId, quote: t7.anchor.quote ?? "" }, status: t7.status, statusAt: t7.statusAt, ...statusBy, messages });
17763
+ threads.push({ id: t7.id, anchor, status: t7.status, statusAt: t7.statusAt, ...statusBy, messages });
17755
17764
  }
17756
17765
  return { version: 1, threads };
17757
17766
  }
@@ -17791,32 +17800,36 @@ function mergeThread(base, incoming, origin, now, newId, warnings, name) {
17791
17800
  messages.sort((a5, b6) => a5.at < b6.at ? -1 : a5.at > b6.at ? 1 : 0);
17792
17801
  let { status, statusAt, statusBy } = base;
17793
17802
  if (incoming.status && incoming.status !== base.status) {
17794
- const stamp = incoming.statusAt && incoming.statusAt !== base.statusAt ? incoming.statusAt : now;
17795
- if (stamp >= base.statusAt) {
17803
+ const stamp2 = incoming.statusAt && incoming.statusAt !== base.statusAt ? incoming.statusAt : now;
17804
+ if (stamp2 >= base.statusAt) {
17796
17805
  status = incoming.status;
17797
- statusAt = stamp;
17806
+ statusAt = stamp2;
17798
17807
  statusBy = origin;
17799
17808
  }
17800
17809
  }
17801
17810
  return { ...base, status, statusAt, ...statusBy ? { statusBy } : {}, messages };
17802
17811
  }
17803
- var sameUnsavedThread = (existing, incoming) => existing.anchor.blockId === incoming.anchor?.blockId && existing.anchor.quote === (incoming.anchor?.quote ?? "") && existing.messages[0]?.text === incoming.messages?.[0]?.text;
17804
- function mergeComments(base, incoming, origin, now, newId, name) {
17812
+ function sameUnsavedThread(existing, incoming, spec) {
17813
+ const anchor = spec.read(incoming.anchor);
17814
+ return !!anchor && spec.same(existing.anchor, anchor) && existing.messages[0]?.text === incoming.messages?.[0]?.text;
17815
+ }
17816
+ function mergeComments(base, incoming, origin, now, newId, name, spec = documentAnchors) {
17805
17817
  const warnings = [];
17806
17818
  const threads = [...base.threads];
17807
17819
  for (const thread of incoming.threads) {
17808
- const index = thread.id ? threads.findIndex((t7) => t7.id === thread.id) : threads.findIndex((t7) => sameUnsavedThread(t7, thread));
17820
+ const index = thread.id ? threads.findIndex((t7) => t7.id === thread.id) : threads.findIndex((t7) => sameUnsavedThread(t7, thread, spec));
17809
17821
  if (index >= 0) {
17810
17822
  threads[index] = mergeThread(threads[index], thread, origin, now, newId, warnings, name);
17811
17823
  continue;
17812
17824
  }
17813
- if (!thread.anchor?.blockId) {
17814
- warnings.push("a new thread needs anchor.blockId; dropped it");
17825
+ const anchor = spec.read(thread.anchor);
17826
+ if (!anchor) {
17827
+ warnings.push(spec.missing);
17815
17828
  continue;
17816
17829
  }
17817
17830
  const fresh = {
17818
17831
  id: thread.id ?? newId(),
17819
- anchor: { blockId: thread.anchor.blockId, quote: thread.anchor.quote ?? "" },
17832
+ anchor,
17820
17833
  status: "open",
17821
17834
  statusAt: now,
17822
17835
  statusBy: origin,
@@ -17834,23 +17847,15 @@ var updateHumanMessage = (thread, messageId, update2) => ({
17834
17847
  ...thread,
17835
17848
  messages: thread.messages.map((m11) => m11.id === messageId && m11.author === "human" && !m11.deleted ? update2(m11) : m11)
17836
17849
  });
17850
+ function openThread(anchor, author, text2, now, newId, who) {
17851
+ return { id: newId(), anchor, status: "open", statusAt: now, statusBy: author, messages: [{ id: newId(), author, ...personOf(who), text: text2, at: now }] };
17852
+ }
17837
17853
  function applyHumanOp(file, op, now, newId, who) {
17854
+ if (op.op === "create") return { ...file, threads: [...file.threads, openThread({ blockId: op.blockId, quote: op.quote }, "human", op.text, now, newId, who)] };
17855
+ return applyThreadOp(file, op, now, newId, who);
17856
+ }
17857
+ function applyThreadOp(file, op, now, newId, who) {
17838
17858
  switch (op.op) {
17839
- case "create":
17840
- return {
17841
- ...file,
17842
- threads: [
17843
- ...file.threads,
17844
- {
17845
- id: newId(),
17846
- anchor: { blockId: op.blockId, quote: op.quote },
17847
- status: "open",
17848
- statusAt: now,
17849
- statusBy: "human",
17850
- messages: [{ id: newId(), author: "human", ...personOf(who), text: op.text, at: now }]
17851
- }
17852
- ]
17853
- };
17854
17859
  case "reply":
17855
17860
  return updateThread(file, op.threadId, (t7) => ({ ...t7, messages: [...t7.messages, { id: newId(), author: "human", ...personOf(who), text: op.text, at: now }] }));
17856
17861
  case "edit":
@@ -17905,7 +17910,7 @@ function unreadThreads(file, cursor) {
17905
17910
  const threads = [];
17906
17911
  let newest = cursor;
17907
17912
  for (const thread of file.threads) {
17908
- for (const stamp of humanStamps(thread)) if (!newest || stamp > newest) newest = stamp;
17913
+ for (const stamp2 of humanStamps(thread)) if (!newest || stamp2 > newest) newest = stamp2;
17909
17914
  const unread = cursor === null ? waitingOn(thread) === "agent" ? waitingSince(thread) : null : changesSince(thread, cursor);
17910
17915
  if (unread && unread.changes.length > 0) threads.push({ ...thread, ...unread });
17911
17916
  }
@@ -17913,16 +17918,12 @@ function unreadThreads(file, cursor) {
17913
17918
  }
17914
17919
  function applyAgentOp(file, op, now, newId, name) {
17915
17920
  if (op.op === "add") {
17916
- const thread = {
17917
- id: newId(),
17918
- anchor: { blockId: op.blockId, quote: op.quote },
17919
- status: "open",
17920
- statusAt: now,
17921
- statusBy: "agent",
17922
- messages: [{ id: newId(), author: "agent", ...personOf({ name }), text: op.text, at: now }]
17923
- };
17921
+ const thread = openThread({ blockId: op.blockId, quote: op.quote }, "agent", op.text, now, newId, { name });
17924
17922
  return { file: { ...file, threads: [...file.threads, thread] }, threadId: thread.id, messageId: thread.messages[0].id };
17925
17923
  }
17924
+ return applyAgentThreadOp(file, op, now, newId, name);
17925
+ }
17926
+ function applyAgentThreadOp(file, op, now, newId, name) {
17926
17927
  if (!file.threads.some((t7) => t7.id === op.threadId)) throw new Error(`no thread ${op.threadId}`);
17927
17928
  if (op.op === "resolve") {
17928
17929
  return { file: updateThread(file, op.threadId, (t7) => ({ ...t7, status: "resolved", statusAt: now, statusBy: "agent" })), threadId: op.threadId };
@@ -17931,6 +17932,76 @@ function applyAgentOp(file, op, now, newId, name) {
17931
17932
  return { file: updateThread(file, op.threadId, (t7) => ({ ...t7, messages: [...t7.messages, message] })), threadId: op.threadId, messageId: message.id };
17932
17933
  }
17933
17934
 
17935
+ // src/shared/board-comments.ts
17936
+ var MAX_ID = 100;
17937
+ var MAX_PATH = 2e3;
17938
+ var MAX_NODES = 50;
17939
+ var isName = (value, max = MAX_ID) => typeof value === "string" && value.length > 0 && value.length <= max;
17940
+ var isFiniteNumber = (value) => typeof value === "number" && Number.isFinite(value);
17941
+ var isFraction = (value) => isFiniteNumber(value) && value >= 0 && value <= 1;
17942
+ function readNodes(value) {
17943
+ if (!Array.isArray(value) || value.length === 0 || value.length > MAX_NODES) return null;
17944
+ const nodes = [];
17945
+ for (const node of value) {
17946
+ if (!isObject(node) || !isName(node.board) || !isName(node.path, MAX_PATH)) return null;
17947
+ nodes.push({ board: node.board, path: node.path });
17948
+ }
17949
+ return nodes;
17950
+ }
17951
+ function readHashes(value) {
17952
+ if (!isObject(value) || Array.isArray(value)) return null;
17953
+ const entries2 = Object.entries(value);
17954
+ if (entries2.length > MAX_NODES || !entries2.every(([board, hash]) => isName(board) && isName(hash))) return null;
17955
+ return Object.fromEntries(entries2);
17956
+ }
17957
+ function readBoardAnchor(value) {
17958
+ if (!isObject(value) || !isName(value.page) || !isFiniteNumber(value.x) || !isFiniteNumber(value.y)) return null;
17959
+ const anchor = { page: value.page, x: value.x, y: value.y };
17960
+ if (value.board !== void 0) {
17961
+ if (!isName(value.board)) return null;
17962
+ anchor.board = value.board;
17963
+ }
17964
+ if (value.nodes !== void 0) {
17965
+ const nodes = readNodes(value.nodes);
17966
+ if (!nodes) return null;
17967
+ anchor.nodes = nodes;
17968
+ }
17969
+ if (value.offset !== void 0) {
17970
+ const offset = value.offset;
17971
+ if (!anchor.nodes || !isObject(offset) || !isFraction(offset.x) || !isFraction(offset.y)) return null;
17972
+ anchor.offset = { x: offset.x, y: offset.y };
17973
+ }
17974
+ if (value.hashes !== void 0) {
17975
+ const hashes = readHashes(value.hashes);
17976
+ if (!hashes) return null;
17977
+ anchor.hashes = hashes;
17978
+ }
17979
+ return anchor;
17980
+ }
17981
+ var boardAnchors = {
17982
+ read: readBoardAnchor,
17983
+ same: (a5, b6) => a5.page === b6.page && a5.board === b6.board && a5.x === b6.x && a5.y === b6.y,
17984
+ missing: "a new thread needs a board anchor (page, x, y); dropped it"
17985
+ };
17986
+ var emptyBoardComments = () => ({ version: 1, threads: [] });
17987
+ var savedBoardComments = (incoming) => savedComments(incoming, boardAnchors);
17988
+ var mergeBoardComments = (base, incoming, origin, now, newId, name) => mergeComments(base, incoming, origin, now, newId, name, boardAnchors);
17989
+ function applyBoardHumanOp(file, op, now, newId, who) {
17990
+ if (op.op === "create") return { ...file, threads: [...file.threads, openThread(op.anchor, "human", op.text, now, newId, who)] };
17991
+ return applyThreadOp(file, op, now, newId, who);
17992
+ }
17993
+ function applyBoardAgentOp(file, op, now, newId, name) {
17994
+ if (op.op === "add") {
17995
+ const thread = openThread(op.anchor, "agent", op.text, now, newId, { name });
17996
+ return { file: { ...file, threads: [...file.threads, thread] }, threadId: thread.id, messageId: thread.messages[0].id };
17997
+ }
17998
+ return applyAgentThreadOp(file, op, now, newId, name);
17999
+ }
18000
+
18001
+ // src/server/comment-cursors.ts
18002
+ var import_node_fs6 = __toESM(require("node:fs"), 1);
18003
+ var import_node_path6 = __toESM(require("node:path"), 1);
18004
+
17934
18005
  // src/server/state.ts
17935
18006
  var import_node_fs5 = __toESM(require("node:fs"), 1);
17936
18007
  var import_node_path5 = __toESM(require("node:path"), 1);
@@ -18282,7 +18353,31 @@ function writeArtifactState(stateDir, ref, payload, extra = {}) {
18282
18353
  });
18283
18354
  }
18284
18355
 
18356
+ // src/server/comment-cursors.ts
18357
+ var cursorsFile = (stateDir, id2, type = "document") => import_node_path6.default.join(stateDir, type, id2, "comment-cursors.json");
18358
+ function read(file) {
18359
+ try {
18360
+ const value = JSON.parse(import_node_fs6.default.readFileSync(file, "utf8"));
18361
+ if (typeof value !== "object" || value === null || Array.isArray(value)) return {};
18362
+ return Object.fromEntries(Object.entries(value).filter((entry) => typeof entry[1] === "string"));
18363
+ } catch {
18364
+ return {};
18365
+ }
18366
+ }
18367
+ function createCommentCursors(stateDir, type = "document") {
18368
+ return {
18369
+ get: (id2, agent) => read(cursorsFile(stateDir, id2, type))[agent] ?? null,
18370
+ set: (id2, agent, stamp2) => {
18371
+ const file = cursorsFile(stateDir, id2, type);
18372
+ const all = read(file);
18373
+ if (all[agent] === stamp2) return;
18374
+ writeJsonAtomic(file, { ...all, [agent]: stamp2 });
18375
+ }
18376
+ };
18377
+ }
18378
+
18285
18379
  // src/server/document-comments.ts
18380
+ var import_node_fs7 = __toESM(require("node:fs"), 1);
18286
18381
  var MAX_TEXT = 2e4;
18287
18382
  var isText3 = (value, max = MAX_TEXT) => typeof value === "string" && value.length <= max;
18288
18383
  var isId = (value) => isText3(value, 100) && value.length > 0;
@@ -18319,20 +18414,30 @@ function asAgentOp(value) {
18319
18414
  return null;
18320
18415
  }
18321
18416
  }
18417
+ var documentRules = {
18418
+ empty: emptyComments,
18419
+ saved: savedComments,
18420
+ merge: mergeComments,
18421
+ human: applyHumanOp,
18422
+ agent: applyAgentOp
18423
+ };
18322
18424
  var noNames = { human: () => void 0, agent: () => void 0 };
18323
18425
  function createCommentStore(file, names = noNames, now = () => (/* @__PURE__ */ new Date()).toISOString(), newId = () => nanoid2(10)) {
18324
- let current = emptyComments();
18426
+ return createStore(file, documentRules, names, now, newId);
18427
+ }
18428
+ function createStore(file, rules, names = noNames, now = () => (/* @__PURE__ */ new Date()).toISOString(), newId = () => nanoid2(10)) {
18429
+ let current = rules.empty();
18325
18430
  let lastSeen = null;
18326
18431
  let warnings = [];
18327
18432
  let loaded = false;
18328
18433
  const write = () => {
18329
18434
  writeJsonAtomic(file, current);
18330
- lastSeen = import_node_fs6.default.readFileSync(file, "utf8");
18435
+ lastSeen = import_node_fs7.default.readFileSync(file, "utf8");
18331
18436
  };
18332
18437
  const sync = () => {
18333
18438
  let text2;
18334
18439
  try {
18335
- text2 = import_node_fs6.default.readFileSync(file, "utf8");
18440
+ text2 = import_node_fs7.default.readFileSync(file, "utf8");
18336
18441
  } catch {
18337
18442
  return false;
18338
18443
  }
@@ -18340,8 +18445,8 @@ function createCommentStore(file, names = noNames, now = () => (/* @__PURE__ */
18340
18445
  lastSeen = text2;
18341
18446
  try {
18342
18447
  const incoming = parseComments(text2);
18343
- const base = loaded ? current : savedComments(incoming);
18344
- const merged = mergeComments(base, incoming, "agent", now(), newId, names.agent());
18448
+ const base = loaded ? current : rules.saved(incoming);
18449
+ const merged = rules.merge(base, incoming, "agent", now(), newId, names.agent());
18345
18450
  current = merged.file;
18346
18451
  warnings = merged.warnings;
18347
18452
  if (`${JSON.stringify(current, null, 2)}
@@ -18359,13 +18464,13 @@ function createCommentStore(file, names = noNames, now = () => (/* @__PURE__ */
18359
18464
  sync,
18360
18465
  apply: (op) => {
18361
18466
  sync();
18362
- current = applyHumanOp(current, op, now(), newId, names.human());
18467
+ current = rules.human(current, op, now(), newId, names.human());
18363
18468
  write();
18364
18469
  return current;
18365
18470
  },
18366
18471
  applyAgent: (op, name) => {
18367
18472
  sync();
18368
- const result = applyAgentOp(current, op, now(), newId, name ?? names.agent());
18473
+ const result = rules.agent(current, op, now(), newId, name ?? names.agent());
18369
18474
  current = result.file;
18370
18475
  write();
18371
18476
  return result;
@@ -18373,38 +18478,166 @@ function createCommentStore(file, names = noNames, now = () => (/* @__PURE__ */
18373
18478
  };
18374
18479
  }
18375
18480
 
18376
- // src/server/documents.ts
18377
- var import_node_fs8 = __toESM(require("node:fs"), 1);
18378
- var import_node_path8 = __toESM(require("node:path"), 1);
18379
-
18380
- // src/server/comment-cursors.ts
18381
- var import_node_fs7 = __toESM(require("node:fs"), 1);
18382
- var import_node_path6 = __toESM(require("node:path"), 1);
18383
- var cursorsFile = (stateDir, id2) => import_node_path6.default.join(stateDir, "document", id2, "comment-cursors.json");
18384
- function read(file) {
18481
+ // src/server/board-comments.ts
18482
+ var boardRules = {
18483
+ empty: emptyBoardComments,
18484
+ saved: savedBoardComments,
18485
+ merge: mergeBoardComments,
18486
+ human: (file, op, now, newId, who) => applyBoardHumanOp(file, op, now, newId, who),
18487
+ agent: applyBoardAgentOp
18488
+ };
18489
+ var withoutHashes = ({ hashes: _hashes, ...anchor }) => anchor;
18490
+ function asBoardHumanOp(value) {
18491
+ if (typeof value !== "object" || value === null) return null;
18492
+ const v6 = value;
18493
+ if (v6.op !== "create") {
18494
+ const op = asHumanOp(value);
18495
+ return op && op.op !== "create" ? op : null;
18496
+ }
18497
+ const anchor = readBoardAnchor(v6.anchor);
18498
+ return anchor && isText3(v6.text) && v6.text.length > 0 ? { op: "create", anchor: withoutHashes(anchor), text: v6.text } : null;
18499
+ }
18500
+ function asBoardAgentOp(value) {
18501
+ if (typeof value !== "object" || value === null) return null;
18502
+ const v6 = value;
18503
+ if (v6.op === "add") {
18504
+ const anchor = readBoardAnchor(v6.anchor);
18505
+ return anchor && isText3(v6.text) && v6.text.length > 0 ? { op: "add", anchor: withoutHashes(anchor), text: v6.text } : null;
18506
+ }
18507
+ if (v6.op === "reply") return isId(v6.threadId) && isText3(v6.text) && v6.text.length > 0 ? { op: "reply", threadId: v6.threadId, text: v6.text } : null;
18508
+ if (v6.op === "resolve") return isId(v6.threadId) ? { op: "resolve", threadId: v6.threadId } : null;
18509
+ return null;
18510
+ }
18511
+ function boardSpecs(dir) {
18385
18512
  try {
18386
- const value = JSON.parse(import_node_fs7.default.readFileSync(file, "utf8"));
18387
- if (typeof value !== "object" || value === null || Array.isArray(value)) return {};
18388
- return Object.fromEntries(Object.entries(value).filter((entry) => typeof entry[1] === "string"));
18513
+ const canvas = JSON.parse(import_node_fs8.default.readFileSync(import_node_path7.default.join(dir, "canvas.json"), "utf8"));
18514
+ return Object.fromEntries(Object.entries(canvas.boards ?? {}).flatMap(
18515
+ ([id2, board]) => typeof board?.file === "string" ? [[id2, { file: board.file, page: typeof board.page === "string" ? board.page : "", w: typeof board.w === "number" ? board.w : 0 }]] : []
18516
+ ));
18389
18517
  } catch {
18390
18518
  return {};
18391
18519
  }
18392
18520
  }
18393
- function createCommentCursors(stateDir) {
18521
+ var boardFiles = (dir) => Object.fromEntries(Object.entries(boardSpecs(dir)).map(([id2, spec]) => [id2, spec.file]));
18522
+ function agentAddOnBoard(value, specs) {
18523
+ if (typeof value !== "object" || value === null) return null;
18524
+ const v6 = value;
18525
+ if (v6.op !== "add" || typeof v6.board !== "string" || v6.anchor !== void 0) return null;
18526
+ const spec = specs[v6.board];
18527
+ if (!spec) return { error: `no board ${v6.board}` };
18528
+ return asBoardAgentOp({ op: "add", anchor: { page: spec.page, board: v6.board, x: v6.x ?? spec.w, y: v6.y ?? 0 }, text: v6.text });
18529
+ }
18530
+ function fileHash(dir, relative) {
18531
+ const file = resolveBoardFile(dir, relative);
18532
+ if (!file) return null;
18533
+ try {
18534
+ return import_node_crypto2.default.createHash("sha1").update(import_node_fs8.default.readFileSync(file)).digest("hex").slice(0, 12);
18535
+ } catch {
18536
+ return null;
18537
+ }
18538
+ }
18539
+ function boardHashes(dir) {
18540
+ return Object.fromEntries(Object.entries(boardFiles(dir)).flatMap(([id2, file]) => {
18541
+ const hash = fileHash(dir, file);
18542
+ return hash ? [[id2, hash]] : [];
18543
+ }));
18544
+ }
18545
+ var boardsOf = (anchor) => [.../* @__PURE__ */ new Set([...anchor.board ? [anchor.board] : [], ...(anchor.nodes ?? []).map((node) => node.board)])];
18546
+ function stamp(anchor, hashes) {
18547
+ const boards = boardsOf(anchor);
18548
+ if (!boards.length) return withoutHashes(anchor);
18549
+ return { ...withoutHashes(anchor), hashes: Object.fromEntries(boards.map((board) => [board, hashes[board] ?? "missing"])) };
18550
+ }
18551
+ var isStale = (thread, hashes) => Object.entries(thread.anchor.hashes ?? {}).some(([board, hash]) => hashes[board] !== hash);
18552
+ function createBoardCommentRegistry(options) {
18553
+ const stores = /* @__PURE__ */ new Map();
18554
+ const hashes = /* @__PURE__ */ new Map();
18555
+ const lastStale = /* @__PURE__ */ new Map();
18556
+ const cursors = createCommentCursors(options.stateDir, "artboard");
18557
+ const dirOf = (id2) => import_node_path7.default.join(options.root, "artboard", id2);
18558
+ const store = (id2) => {
18559
+ let found = stores.get(id2);
18560
+ if (!found) {
18561
+ found = createStore(import_node_path7.default.join(dirOf(id2), "comments.json"), boardRules, options.names);
18562
+ stores.set(id2, found);
18563
+ }
18564
+ return found;
18565
+ };
18566
+ const currentHashes = (id2) => {
18567
+ let found = hashes.get(id2);
18568
+ if (!found) {
18569
+ found = boardHashes(dirOf(id2));
18570
+ hashes.set(id2, found);
18571
+ }
18572
+ return found;
18573
+ };
18574
+ const stale = (id2) => {
18575
+ const now = currentHashes(id2);
18576
+ return store(id2).current().threads.filter((thread) => isStale(thread, now)).map((thread) => thread.id);
18577
+ };
18578
+ const push2 = (id2) => {
18579
+ const ids = stale(id2);
18580
+ lastStale.set(id2, JSON.stringify(ids));
18581
+ options.broadcast({ type: "board-comments", artifact: `artboard/${id2}`, comments: store(id2).current(), stale: ids });
18582
+ };
18583
+ const fresh = (id2) => {
18584
+ hashes.delete(id2);
18585
+ return currentHashes(id2);
18586
+ };
18587
+ const forAgent2 = (id2, staleIds) => {
18588
+ const files = boardFiles(dirOf(id2));
18589
+ return (thread) => ({
18590
+ ...thread,
18591
+ stale: staleIds.has(thread.id),
18592
+ waitingOn: waitingOn(thread),
18593
+ files: Object.fromEntries(boardsOf(thread.anchor).flatMap((board) => files[board] ? [[board, files[board]]] : [])),
18594
+ messages: thread.messages.map(({ avatar: _avatar, ...message }) => message)
18595
+ });
18596
+ };
18394
18597
  return {
18395
- get: (id2, agent) => read(cursorsFile(stateDir, id2))[agent] ?? null,
18396
- set: (id2, agent, stamp) => {
18397
- const file = cursorsFile(stateDir, id2);
18398
- const all = read(file);
18399
- if (all[agent] === stamp) return;
18400
- writeJsonAtomic(file, { ...all, [agent]: stamp });
18598
+ comments: (id2) => store(id2).current(),
18599
+ stale,
18600
+ warnings: (id2) => store(id2).warnings(),
18601
+ apply: (id2, op) => {
18602
+ const stamped = op.op === "create" ? { ...op, anchor: stamp(op.anchor, fresh(id2)) } : op;
18603
+ store(id2).apply(stamped);
18604
+ push2(id2);
18605
+ return { comments: store(id2).current(), stale: stale(id2) };
18606
+ },
18607
+ applyAgent: (id2, op, agent) => {
18608
+ const stamped = op.op === "add" ? { ...op, anchor: stamp(op.anchor, fresh(id2)) } : op;
18609
+ const { threadId, messageId } = store(id2).applyAgent(stamped, agent);
18610
+ push2(id2);
18611
+ return { threadId, ...messageId ? { messageId } : {} };
18612
+ },
18613
+ list: (id2, agent, all) => {
18614
+ const changed = store(id2).sync();
18615
+ if (changed) push2(id2);
18616
+ const file = store(id2).current();
18617
+ const format = forAgent2(id2, new Set(stale(id2)));
18618
+ if (all) return file.threads.filter((t7) => !t7.messages.every((m11) => m11.deleted)).map(format);
18619
+ const { threads, newest } = unreadThreads(file, cursors.get(id2, agent));
18620
+ if (newest) cursors.set(id2, agent, newest);
18621
+ return threads.map(format);
18622
+ },
18623
+ sync: (id2) => {
18624
+ if (store(id2).sync()) push2(id2);
18625
+ },
18626
+ boardsChanged: (id2) => {
18627
+ fresh(id2);
18628
+ if (!stores.has(id2) && !import_node_fs8.default.existsSync(import_node_path7.default.join(dirOf(id2), "comments.json"))) return;
18629
+ if (JSON.stringify(stale(id2)) !== (lastStale.get(id2) ?? "[]")) push2(id2);
18401
18630
  }
18402
18631
  };
18403
18632
  }
18404
18633
 
18634
+ // src/server/documents.ts
18635
+ var import_node_fs9 = __toESM(require("node:fs"), 1);
18636
+ var import_node_path9 = __toESM(require("node:path"), 1);
18637
+
18405
18638
  // src/server/document-session.ts
18406
18639
  var import_promises = __toESM(require("node:fs/promises"), 1);
18407
- var import_node_path7 = __toESM(require("node:path"), 1);
18640
+ var import_node_path8 = __toESM(require("node:path"), 1);
18408
18641
 
18409
18642
  // node_modules/.pnpm/lexical@0.52.0_typescript@5.9.3/node_modules/lexical/dist/Lexical.dev.js
18410
18643
  var Lexical_dev_exports = {};
@@ -19262,19 +19495,19 @@ var ElementDOMSlot = class _ElementDOMSlot extends DOMSlot {
19262
19495
  }
19263
19496
  };
19264
19497
  function indexPath(root, child) {
19265
- const path21 = [];
19498
+ const path22 = [];
19266
19499
  let node = child;
19267
19500
  for (; node !== root && node !== null; node = node.parentNode) {
19268
19501
  let i6 = 0;
19269
19502
  for (let sibling = node.previousSibling; sibling !== null; sibling = sibling.previousSibling) {
19270
19503
  i6++;
19271
19504
  }
19272
- path21.push(i6);
19505
+ path22.push(i6);
19273
19506
  }
19274
19507
  if (!(node === root)) {
19275
19508
  formatDevErrorMessage(`indexPath: root is not a parent of child`);
19276
19509
  }
19277
- return path21.reverse();
19510
+ return path22.reverse();
19278
19511
  }
19279
19512
  // @__NO_SIDE_EFFECTS__
19280
19513
  function readLexicalVersion() {
@@ -34054,9 +34287,9 @@ function getActiveElementDeep(root) {
34054
34287
  function getComposedEventTarget(event) {
34055
34288
  const target = event.target;
34056
34289
  if (target !== null && isHTMLElement(target) && target.shadowRoot !== null && typeof event.composedPath === "function") {
34057
- const path21 = event.composedPath();
34058
- if (path21.length > 0) {
34059
- return path21[0];
34290
+ const path22 = event.composedPath();
34291
+ if (path22.length > 0) {
34292
+ return path22[0];
34060
34293
  }
34061
34294
  }
34062
34295
  return target;
@@ -35967,7 +36200,7 @@ __export(Lexical_prod_exports, {
35967
36200
  $getNodeFromDOMNode: () => Qc,
35968
36201
  $getPreviousSelection: () => ds,
35969
36202
  $getRoot: () => ra,
35970
- $getSelection: () => fs8,
36203
+ $getSelection: () => fs9,
35971
36204
  $getSelectionSlotFrame: () => Df,
35972
36205
  $getSiblingCaret: () => ad,
35973
36206
  $getSlot: () => Lf,
@@ -36707,9 +36940,9 @@ function Ct(t7, e5, n7) {
36707
36940
  const o8 = performance.now() - _t > 100;
36708
36941
  try {
36709
36942
  al(t7, () => {
36710
- const r7 = fs8() || (function(t8) {
36943
+ const r7 = fs9() || (function(t8) {
36711
36944
  return t8.read("latest", () => {
36712
- const t9 = fs8();
36945
+ const t9 = fs9();
36713
36946
  return null !== t9 ? t9.clone() : null;
36714
36947
  });
36715
36948
  })(t7), i6 = /* @__PURE__ */ new Map(), s7 = t7._editorState, l8 = t7._blockCursorElement;
@@ -37989,7 +38222,7 @@ function Yo(t7, n7, o8) {
37989
38222
  al(n7, () => {
37990
38223
  if (!o8) return void sa(null);
37991
38224
  if (!Dc(n7, r7, s7)) return;
37992
- let a5 = fs8();
38225
+ let a5 = fs9();
37993
38226
  if (c10.postDeleteSelectionToRestore && zi(a5) && a5.isCollapsed()) {
37994
38227
  const t8 = a5.anchor, e5 = c10.postDeleteSelectionToRestore.anchor;
37995
38228
  (t8.key === e5.key && t8.offset === e5.offset + 1 || 1 === t8.offset && e5.getNode().is(t8.getNode().getPreviousSibling())) && (a5 = c10.postDeleteSelectionToRestore.clone(), sa(a5));
@@ -38031,7 +38264,7 @@ function Xo(t7, e5) {
38031
38264
  }
38032
38265
  function Qo(t7, e5) {
38033
38266
  al(e5, () => {
38034
- const n7 = fs8(), o8 = Qa(Wa(e5)), r7 = ds();
38267
+ const n7 = fs9(), o8 = Qa(Wa(e5)), r7 = ds();
38035
38268
  if (o8) {
38036
38269
  if (zi(n7)) {
38037
38270
  const t8 = n7.anchor, e6 = t8.getNode();
@@ -38080,7 +38313,7 @@ function er(t7) {
38080
38313
  if (null == t7 || t7.length <= 1 || null == e5) return;
38081
38314
  const n7 = 1 === e5.length ? e5 : "Enter" === e5 ? "\n" : "Tab" === e5 ? " " : null;
38082
38315
  if (!n7) return;
38083
- const o8 = fs8();
38316
+ const o8 = fs9();
38084
38317
  if (!zi(o8) || !o8.isCollapsed()) return;
38085
38318
  const r7 = o8.anchor.getNode();
38086
38319
  if (!ki(r7)) return;
@@ -38112,7 +38345,7 @@ function rr(t7, e5) {
38112
38345
  return null !== r7 && o8.contains(r7) && Ju(r7, e5);
38113
38346
  }
38114
38347
  function ir(t7) {
38115
- const n7 = t7.inputType, o8 = tr(t7), r7 = Vs(), i6 = r7._inputState, s7 = fs8();
38348
+ const n7 = t7.inputType, o8 = tr(t7), r7 = Vs(), i6 = r7._inputState, s7 = fs9();
38116
38349
  if ("insertText" === n7 && t7.data && i6.isInsertTextAfterHandledSelectionCommand) {
38117
38350
  if (nr(i6), t7.preventDefault(), zi(s7) && !s7.isCollapsed()) {
38118
38351
  const t8 = s7.isBackward() ? s7.anchor : s7.focus;
@@ -38146,7 +38379,7 @@ function ir(t7) {
38146
38379
  let u9 = m && n8 && !a6 && c11;
38147
38380
  if (u9 && s7.isCollapsed() && (u9 = !Sl(Ea(s7.anchor, true))), !u9) {
38148
38381
  Ma(r7, jn, true);
38149
- const t8 = fs8();
38382
+ const t8 = fs9();
38150
38383
  m && zi(t8) && t8.isCollapsed() && (i6.postDeleteSelectionToRestore = t8, setTimeout(() => i6.postDeleteSelectionToRestore = null));
38151
38384
  }
38152
38385
  }
@@ -38246,7 +38479,7 @@ function sr(t7, e5) {
38246
38479
  }, { event: t7 }), n7.unprocessedBeforeInputData = null;
38247
38480
  }
38248
38481
  function lr(t7) {
38249
- const e5 = Vs(), n7 = e5._inputState, o8 = fs8(), r7 = t7.data, i6 = tr(t7);
38482
+ const e5 = Vs(), n7 = e5._inputState, o8 = fs9(), r7 = t7.data, i6 = tr(t7);
38250
38483
  let s7 = false;
38251
38484
  if (null != r7 && zi(o8)) {
38252
38485
  const l8 = Qa(Wa(e5)), c10 = null !== l8 ? cu(l8, e5._rootElement) : null, a5 = "insertCompositionText" === t7.inputType && "ending-firefox" !== n7.compositionPhase && !e5.isComposing();
@@ -38271,13 +38504,13 @@ function cr(t7, e5) {
38271
38504
  Ma(e5, Wn, t7);
38272
38505
  }
38273
38506
  function ar(t7) {
38274
- const e5 = Vs(), n7 = e5._inputState, o8 = fs8();
38507
+ const e5 = Vs(), n7 = e5._inputState, o8 = fs9();
38275
38508
  if (zi(o8) && !e5.isComposing()) {
38276
38509
  n7.compositionPhase = "composing", n7.hadOrphanedCompositionEvents = false;
38277
38510
  const r7 = o8.anchor, i6 = o8.anchor.getNode();
38278
38511
  if (Yc(r7.key), za(Ur), t7.timeStamp < n7.lastKeyDownTimeStamp + 30 || "element" === r7.type || !o8.isCollapsed() || !m && (i6.getFormat() !== o8.format || ki(i6) && i6.getStyle() !== o8.style) || ki(i6) && (Bc(i6) || 0 === r7.offset && !i6.canInsertTextBefore() || r7.offset === i6.getTextContentSize() && !i6.canInsertTextAfter())) {
38279
38512
  Ma(e5, Jn, F);
38280
- const t8 = fs8();
38513
+ const t8 = fs9();
38281
38514
  zi(t8) && Yc(t8.anchor.key);
38282
38515
  }
38283
38516
  }
@@ -38291,7 +38524,7 @@ function fr(t7) {
38291
38524
  if (null === t7) return;
38292
38525
  const e5 = Xc(t7);
38293
38526
  if (!ki(e5) || "text" === e5.getType() || Bc(e5) || !e5.isAttached()) return;
38294
- const n7 = fs8(), o8 = zi(n7) && n7.anchor.key === t7 ? n7.anchor.offset : null, r7 = vi(e5.getTextContent());
38527
+ const n7 = fs9(), o8 = zi(n7) && n7.anchor.key === t7 ? n7.anchor.offset : null, r7 = vi(e5.getTextContent());
38295
38528
  if (r7.setFormat(e5.getFormat()), r7.setStyle(e5.getStyle()), e5.replace(r7), null !== o8) {
38296
38529
  const t8 = Math.min(o8, r7.getTextContentSize());
38297
38530
  r7.select(t8, t8);
@@ -38310,7 +38543,7 @@ function dr(t7, e5) {
38310
38543
  return fr(n7), false;
38311
38544
  }
38312
38545
  if ("\n" === e5[e5.length - 1]) {
38313
- const e6 = fs8();
38546
+ const e6 = fs9();
38314
38547
  if (zi(e6) || Bi(e6)) {
38315
38548
  if (zi(e6)) {
38316
38549
  const t8 = e6.focus;
@@ -38322,7 +38555,7 @@ function dr(t7, e5) {
38322
38555
  const o8 = Xc(n7);
38323
38556
  if (null !== o8 && ki(o8) && Bc(o8)) {
38324
38557
  o8.markDirty();
38325
- const t8 = fs8(), r7 = o8.getTextContentSize(), i6 = zi(t8) && t8.anchor.key === n7 ? t8.anchor.offset : r7;
38558
+ const t8 = fs9(), r7 = o8.getTextContentSize(), i6 = zi(t8) && t8.anchor.key === n7 ? t8.anchor.offset : r7;
38326
38559
  return o8.select(i6, i6).insertText(e5), true;
38327
38560
  }
38328
38561
  }
@@ -38449,7 +38682,7 @@ function wr(t7, n7, o8) {
38449
38682
  const r7 = t7.__key, i6 = t7.getParent();
38450
38683
  if (null === i6) return void (null !== Of(t7) && e(367, r7, String(Of(t7))));
38451
38684
  const s7 = (function(t8) {
38452
- const e5 = fs8();
38685
+ const e5 = fs9();
38453
38686
  if (!zi(e5) || !yl(t8)) return e5;
38454
38687
  const { anchor: n8, focus: o9 } = e5, r8 = n8.getNode(), i7 = o9.getNode();
38455
38688
  return Ba(r8, t8) && n8.set(t8.__key, 0, "element"), Ba(i7, t8) && o9.set(t8.__key, 0, "element"), e5;
@@ -38521,7 +38754,7 @@ var Fr = class {
38521
38754
  return false;
38522
38755
  }
38523
38756
  isSelected(t7) {
38524
- const e5 = t7 || fs8();
38757
+ const e5 = t7 || fs9();
38525
38758
  if (null == e5) return false;
38526
38759
  const n7 = e5.getNodes().some((t8) => t8.__key === this.__key);
38527
38760
  if (ki(this)) return n7;
@@ -38711,7 +38944,7 @@ var Fr = class {
38711
38944
  }
38712
38945
  replace(t7, n7) {
38713
38946
  Ws();
38714
- let o8 = fs8();
38947
+ let o8 = fs9();
38715
38948
  null !== o8 && (o8 = o8.clone()), qa(this, t7);
38716
38949
  const r7 = this.getLatest(), i6 = this.__key, s7 = Mf(r7);
38717
38950
  null !== s7 && e(400, i6, r7.getType(), s7.getKey(), s7.getType());
@@ -38731,7 +38964,7 @@ var Fr = class {
38731
38964
  Ws(), qa(this, t7);
38732
38965
  const n7 = this.getWritable(), o8 = t7.getWritable();
38733
38966
  this.getParentOrThrow();
38734
- const r7 = o8.getParent(), i6 = fs8();
38967
+ const r7 = o8.getParent(), i6 = fs9();
38735
38968
  let s7 = false, l8 = false, c10 = -1;
38736
38969
  if (null !== r7 && e5 && zi(i6) && hs(i6, r7)) {
38737
38970
  const e6 = r7.__key, n8 = i6.anchor, o9 = i6.focus;
@@ -38752,7 +38985,7 @@ var Fr = class {
38752
38985
  Ws(), qa(this, t7);
38753
38986
  const n7 = this.getWritable(), o8 = t7.getWritable();
38754
38987
  this.getParentOrThrow();
38755
- const r7 = o8.__key, i6 = fs8(), s7 = o8.getParent(), l8 = null !== s7 && e5 && zi(i6) && hs(i6, s7), c10 = l8 ? o8.getIndexWithinParent() : -1;
38988
+ const r7 = o8.__key, i6 = fs9(), s7 = o8.getParent(), l8 = null !== s7 && e5 && zi(i6) && hs(i6, s7), c10 = l8 ? o8.getIndexWithinParent() : -1;
38756
38989
  Jc(o8), l8 && null !== s7 && zi(i6) && gs(i6, s7, c10, -1);
38757
38990
  const a5 = this.getPreviousSibling(), u8 = this.getParentOrThrow().getWritable(), f6 = n7.__prev, d9 = e5 && zi(i6) && hs(i6, u8), h9 = d9 ? this.getIndexWithinParent() : -1;
38758
38991
  return null === a5 ? u8.__first = r7 : a5.getWritable().__next = r7, u8.__size++, n7.__prev = r7, o8.__prev = f6, o8.__next = n7.__key, o8.__parent = n7.__parent, d9 && zi(i6) && gs(i6, u8, h9), t7;
@@ -39150,7 +39383,7 @@ var hi = class extends Fr {
39150
39383
  select(t7, e5) {
39151
39384
  Ws();
39152
39385
  let n7 = t7, o8 = e5;
39153
- const r7 = fs8(), i6 = this.getTextContent(), s7 = this.__key;
39386
+ const r7 = fs9(), i6 = this.getTextContent(), s7 = this.__key;
39154
39387
  if ("string" == typeof i6) {
39155
39388
  const t8 = i6.length;
39156
39389
  void 0 === n7 && (n7 = t8), void 0 === o8 && (o8 = t8);
@@ -39173,7 +39406,7 @@ var hi = class extends Fr {
39173
39406
  const r7 = this.getWritable(), i6 = r7.__text, s7 = n7.length;
39174
39407
  let l8 = t7;
39175
39408
  l8 < 0 && (l8 = s7 + l8, l8 < 0 && (l8 = 0));
39176
- const c10 = fs8();
39409
+ const c10 = fs9();
39177
39410
  if (o8 && zi(c10)) {
39178
39411
  const e6 = t7 + s7;
39179
39412
  c10.setTextNodeRange(r7, e6, r7, e6);
@@ -39204,7 +39437,7 @@ var hi = class extends Fr {
39204
39437
  let f6;
39205
39438
  const d9 = e5.getFormat(), h9 = e5.getStyle(), g8 = e5.__detail;
39206
39439
  let _5 = false, p7 = null, y5 = null;
39207
- const m11 = fs8();
39440
+ const m11 = fs9();
39208
39441
  if (zi(m11)) {
39209
39442
  const [t8, e6] = m11.isBackward() ? [m11.focus, m11.anchor] : [m11.anchor, m11.focus];
39210
39443
  "text" === t8.type && t8.key === o8 && (p7 = t8), "text" === e6.type && e6.key === o8 && (y5 = e6);
@@ -39243,7 +39476,7 @@ var hi = class extends Fr {
39243
39476
  n7 || t7 === this.getNextSibling() || e(50);
39244
39477
  const o8 = this.__key, r7 = t7.__key, i6 = this.__text, s7 = i6.length;
39245
39478
  Gc() === r7 && Yc(o8);
39246
- const l8 = fs8();
39479
+ const l8 = fs9();
39247
39480
  if (zi(l8)) {
39248
39481
  const e5 = l8.anchor, i7 = l8.focus;
39249
39482
  null !== e5 && e5.key === r7 && ys(e5, n7, o8, t7, s7), null !== i7 && i7.key === r7 && ys(i7, n7, o8, t7, s7);
@@ -39530,7 +39763,7 @@ var Ii = class _Ii {
39530
39763
  }
39531
39764
  deleteNodes() {
39532
39765
  const t7 = this.getNodes().filter((t8) => null === Of(t8));
39533
- if ((fs8() || ds()) === this && t7[0]) {
39766
+ if ((fs9() || ds()) === this && t7[0]) {
39534
39767
  const e5 = ad(t7[0], "next");
39535
39768
  Kl(vd(e5, e5));
39536
39769
  }
@@ -39540,7 +39773,7 @@ var Ii = class _Ii {
39540
39773
  };
39541
39774
  function Li() {
39542
39775
  const t7 = ra();
39543
- if (0 === t7.getChildrenSize() && null === Df(fs8())) {
39776
+ if (0 === t7.getChildrenSize() && null === Df(fs9())) {
39544
39777
  const e5 = oc();
39545
39778
  t7.append(e5), e5.select();
39546
39779
  }
@@ -39667,8 +39900,8 @@ var Ki = class _Ki {
39667
39900
  else {
39668
39901
  const t8 = vi(r7.getTextContent());
39669
39902
  t8.setFormat(n7), t8.setStyle(o8);
39670
- const e6 = fs8() === this;
39671
- r7.replace(t8), this.setTextNodeRange(t8, i6, t8, i6), e6 && fs8() !== this && sa(this);
39903
+ const e6 = fs9() === this;
39904
+ r7.replace(t8), this.setTextNodeRange(t8, i6, t8, i6), e6 && fs9() !== this && sa(this);
39672
39905
  }
39673
39906
  return void ("" !== t7 && this.insertText(t7));
39674
39907
  }
@@ -39703,14 +39936,14 @@ var Ki = class _Ki {
39703
39936
  r7.spliceText(i6, 0, t7, true), r7.isComposing() && "text" === this.anchor.type && this.anchor.set(this.anchor.key, this.anchor.offset - t7.length, this.anchor.type);
39704
39937
  }
39705
39938
  removeText() {
39706
- const t7 = fs8() === this, e5 = this.anchor.key;
39939
+ const t7 = fs9() === this, e5 = this.anchor.key;
39707
39940
  Bl(this, Ul(Rl(this))), this.isCollapsed() && (function(t8, e6) {
39708
39941
  const n7 = t8.anchor;
39709
39942
  if (n7.key === e6) return;
39710
39943
  const o8 = n7.getNode();
39711
39944
  let r7 = 0, i6 = "";
39712
39945
  ki(o8) ? (r7 = o8.getFormat(), i6 = o8.getStyle()) : yl(o8) && (r7 = o8.getTextFormat(), i6 = o8.getTextStyle()), t8.format === r7 && t8.style === i6 || (t8.format = r7, t8.style = i6, t8.dirty = true);
39713
- })(this, e5), t7 && fs8() !== this && sa(this);
39946
+ })(this, e5), t7 && fs9() !== this && sa(this);
39714
39947
  }
39715
39948
  formatText(t7, e5 = null) {
39716
39949
  $i(this, t7, e5);
@@ -39727,7 +39960,7 @@ var Ki = class _Ki {
39727
39960
  }
39728
39961
  if (null !== o9) {
39729
39962
  o9.selectStart();
39730
- const n8 = fs8();
39963
+ const n8 = fs9();
39731
39964
  return zi(n8) || e(369), n8.insertNodes(t7);
39732
39965
  }
39733
39966
  }
@@ -40321,7 +40554,7 @@ function us(t7, e5, n7, o8) {
40321
40554
  const x9 = new Ki(g8, _5, y5, m11);
40322
40555
  return p7 && (x9.dirty = true), x9;
40323
40556
  }
40324
- function fs8() {
40557
+ function fs9() {
40325
40558
  return js()._selection;
40326
40559
  }
40327
40560
  function ds() {
@@ -40491,7 +40724,7 @@ function Ss(t7, e5, n7, o8, r7, i6) {
40491
40724
  }
40492
40725
  }
40493
40726
  function Cs(t7) {
40494
- let e5 = fs8() || ds();
40727
+ let e5 = fs9() || ds();
40495
40728
  null === e5 && (e5 = ra().selectEnd()), e5.insertNodes(t7);
40496
40729
  }
40497
40730
  function vs(t7, e5) {
@@ -40502,7 +40735,7 @@ function ks(t7) {
40502
40735
  return vs(t7, { linebreak: () => e5.push(Al()), tab: () => e5.push(wi()), text: (t8) => e5.push(vi(t8)) }), e5;
40503
40736
  }
40504
40737
  function bs() {
40505
- const t7 = fs8();
40738
+ const t7 = fs9();
40506
40739
  return null === t7 ? "" : t7.getTextContent();
40507
40740
  }
40508
40741
  function Ts(t7) {
@@ -40513,7 +40746,7 @@ function Ts(t7) {
40513
40746
  function Ns(t7, n7 = false) {
40514
40747
  let o8 = t7;
40515
40748
  t7.isCollapsed() || o8.removeText();
40516
- const r7 = fs8();
40749
+ const r7 = fs9();
40517
40750
  zi(r7) && (o8 = r7), zi(o8) || e(161);
40518
40751
  const i6 = o8.anchor;
40519
40752
  let s7 = i6.getNode(), l8 = i6.offset;
@@ -41196,7 +41429,7 @@ var pl = class extends Fr {
41196
41429
  }
41197
41430
  select(t7, e5) {
41198
41431
  Ws();
41199
- const n7 = fs8();
41432
+ const n7 = fs9();
41200
41433
  let o8 = t7, r7 = e5;
41201
41434
  const i6 = this.getChildrenSize();
41202
41435
  if (!this.canBeEmpty()) {
@@ -41293,7 +41526,7 @@ var pl = class extends Fr {
41293
41526
  } else t8.__prev = null;
41294
41527
  }
41295
41528
  if (i6.__size = f6, c10.length) {
41296
- const t8 = fs8();
41529
+ const t8 = fs9();
41297
41530
  if (zi(t8)) {
41298
41531
  const e5 = new Set(c10), n8 = new Set(l8), { anchor: o9, focus: r8 } = t8;
41299
41532
  ml(o9, e5, n8) && ps(o9, o9.getNode(), this, u8, a5), ml(r8, e5, n8) && ps(r8, r8.getNode(), this, u8, a5), 0 !== f6 || this.canBeEmpty() || Va(this) || this.remove();
@@ -41576,7 +41809,7 @@ function zl(t7, n7) {
41576
41809
  nd(n7) ? t7.set(o8.getKey(), n7.offset, "text") : rd(n7) ? ki(o8) ? t7.set(o8.getKey(), fd(o8, r7), "text") : t7.set(o8.getParentOrThrow().getKey(), o8.getIndexWithinParent() + (i6 ? 1 : 0), "element") : (id(n7) && yl(o8) || e(268), t7.set(o8.getKey(), i6 ? 0 : o8.getChildrenSize(), "element"));
41577
41810
  }
41578
41811
  function Kl(t7) {
41579
- const e5 = fs8(), n7 = zi(e5) ? e5 : ls();
41812
+ const e5 = fs9(), n7 = zi(e5) ? e5 : ls();
41580
41813
  return Bl(n7, t7), sa(n7), n7;
41581
41814
  }
41582
41815
  function Bl(t7, e5) {
@@ -42196,7 +42429,7 @@ var vc = class {
42196
42429
  focus(t7, e5 = {}) {
42197
42430
  const n7 = this._rootElement;
42198
42431
  null !== n7 && (n7.setAttribute("autocapitalize", "off"), al(this, () => {
42199
- const o8 = fs8(), r7 = ra();
42432
+ const o8 = fs9(), r7 = ra();
42200
42433
  null !== o8 ? o8.dirty || sa(o8.clone()) : 0 !== r7.getChildrenSize() && ("rootStart" === e5.defaultSelection ? r7.selectStart() : r7.selectEnd()), za("focus"), Ka(() => {
42201
42434
  n7.removeAttribute("autocapitalize"), t7 && t7();
42202
42435
  });
@@ -42458,7 +42691,7 @@ function ga(t7, e5, n7, o8, r7) {
42458
42691
  }
42459
42692
  const c10 = i6.getTextContent();
42460
42693
  if (r7 || l8 !== c10) {
42461
- const e6 = fs8();
42694
+ const e6 = fs9();
42462
42695
  if ("" === l8) {
42463
42696
  if (Yc(null), p || g || x) i6.remove();
42464
42697
  else {
@@ -44152,10 +44385,10 @@ function _loadWasmModule(sync, filepath, src, imports2) {
44152
44385
  var buf = null;
44153
44386
  var isNode2 = typeof process !== "undefined" && process.versions != null && process.versions.node != null;
44154
44387
  if (filepath && isNode2) {
44155
- var fs27 = null;
44156
- var path21 = null;
44388
+ var fs28 = null;
44389
+ var path22 = null;
44157
44390
  return new Promise((resolve, reject) => {
44158
- fs27.readFile(path21.resolve(__dirname, filepath), (error, buffer) => {
44391
+ fs28.readFile(path22.resolve(__dirname, filepath), (error, buffer) => {
44159
44392
  if (error != null) {
44160
44393
  reject(error);
44161
44394
  } else {
@@ -45458,10 +45691,10 @@ var LoroDoc = class _LoroDoc {
45458
45691
  * @param {string} path
45459
45692
  * @returns {Value | Container | undefined}
45460
45693
  */
45461
- getByPath(path21) {
45694
+ getByPath(path22) {
45462
45695
  try {
45463
45696
  const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
45464
- const ptr0 = passStringToWasm0(path21, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
45697
+ const ptr0 = passStringToWasm0(path22, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
45465
45698
  const len0 = WASM_VECTOR_LEN;
45466
45699
  wasm.lorodoc_getByPath(retptr, this.__wbg_ptr, ptr0, len0);
45467
45700
  var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true);
@@ -87436,7 +87669,7 @@ async function readText(file) {
87436
87669
  }
87437
87670
  }
87438
87671
  async function writeAtomic(file, data) {
87439
- await import_promises.default.mkdir(import_node_path7.default.dirname(file), { recursive: true });
87672
+ await import_promises.default.mkdir(import_node_path8.default.dirname(file), { recursive: true });
87440
87673
  const tmp = `${file}.${process.pid}.tmp`;
87441
87674
  await import_promises.default.writeFile(tmp, data);
87442
87675
  await import_promises.default.rename(tmp, file);
@@ -87645,13 +87878,13 @@ function outcomeWarnings(outcome, streaming) {
87645
87878
  function createDocumentRegistry(options) {
87646
87879
  const sessions = /* @__PURE__ */ new Map();
87647
87880
  const warnings = /* @__PURE__ */ new Map();
87648
- const docFile = (id2) => import_node_path8.default.join(options.root, "document", id2, "doc.xml");
87881
+ const docFile = (id2) => import_node_path9.default.join(options.root, "document", id2, "doc.xml");
87649
87882
  const commentStores = /* @__PURE__ */ new Map();
87650
87883
  const cursors = createCommentCursors(options.stateDir);
87651
87884
  const commentStore = (id2) => {
87652
87885
  let store = commentStores.get(id2);
87653
87886
  if (!store) {
87654
- store = createCommentStore(import_node_path8.default.join(options.root, "document", id2, "comments.json"), options.names);
87887
+ store = createCommentStore(import_node_path9.default.join(options.root, "document", id2, "comments.json"), options.names);
87655
87888
  commentStores.set(id2, store);
87656
87889
  }
87657
87890
  return store;
@@ -87665,10 +87898,10 @@ function createDocumentRegistry(options) {
87665
87898
  if (JSON.stringify(store.warnings()) !== before2) options.onWarningsChange(id2);
87666
87899
  };
87667
87900
  const create3 = async (id2) => {
87668
- if (!import_node_fs8.default.existsSync(docFile(id2))) return null;
87901
+ if (!import_node_fs9.default.existsSync(docFile(id2))) return null;
87669
87902
  const session = await createDocumentSession({
87670
87903
  file: docFile(id2),
87671
- snapshotFile: import_node_path8.default.join(options.stateDir, "document", id2, "doc.loro"),
87904
+ snapshotFile: import_node_path9.default.join(options.stateDir, "document", id2, "doc.loro"),
87672
87905
  ...options.timing,
87673
87906
  log: options.log
87674
87907
  });
@@ -87711,7 +87944,7 @@ function createDocumentRegistry(options) {
87711
87944
  const known = sessions.has(id2);
87712
87945
  void open(id2).then((session) => {
87713
87946
  if (!session || !known) return;
87714
- import_node_fs8.default.readFile(docFile(id2), "utf8", (err, text2) => {
87947
+ import_node_fs9.default.readFile(docFile(id2), "utf8", (err, text2) => {
87715
87948
  if (!err) session.onFileText(text2);
87716
87949
  });
87717
87950
  });
@@ -87746,7 +87979,7 @@ function createDocumentRegistry(options) {
87746
87979
  }
87747
87980
 
87748
87981
  // src/inspector/runtime.ts
87749
- function installInspector(win, properties) {
87982
+ function installInspector(win, properties, commentCursor = "crosshair") {
87750
87983
  const doc = win.document;
87751
87984
  const session = `${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`;
87752
87985
  const ids = /* @__PURE__ */ new WeakMap();
@@ -87755,6 +87988,7 @@ function installInspector(win, properties) {
87755
87988
  const ignored2 = /* @__PURE__ */ new Set(["SCRIPT", "STYLE", "META", "LINK", "NOSCRIPT", "HEAD"]);
87756
87989
  let nextId = 0;
87757
87990
  let enabled = false;
87991
+ let mode = "select";
87758
87992
  let connection = "";
87759
87993
  let selected = null;
87760
87994
  let treeTimer = 0;
@@ -87926,6 +88160,7 @@ function installInspector(win, properties) {
87926
88160
  suppressClick = false;
87927
88161
  return;
87928
88162
  }
88163
+ if (mode === "comment") return;
87929
88164
  selected = event.target.closest("svg") || event.target;
87930
88165
  selection("user", event.shiftKey || event.metaKey || event.ctrlKey);
87931
88166
  };
@@ -87985,7 +88220,7 @@ function installInspector(win, properties) {
87985
88220
  };
87986
88221
  const key2 = (event) => {
87987
88222
  if (!enabled || event.composedPath().some((target) => target instanceof win.Element && target.matches("input,textarea,select,[contenteditable]"))) return;
87988
- if (!["Space", "KeyH", "KeyV", "Escape", "Equal", "Minus", "Digit0", "Digit1"].includes(event.code)) return;
88223
+ if (!["Space", "KeyH", "KeyV", "KeyC", "Escape", "Equal", "Minus", "Digit0", "Digit1"].includes(event.code)) return;
87989
88224
  event.preventDefault();
87990
88225
  event.stopImmediatePropagation();
87991
88226
  post("key", { key: event.key, code: event.code, down: event.type === "keydown", ctrlKey: event.ctrlKey, metaKey: event.metaKey, shiftKey: event.shiftKey, altKey: event.altKey });
@@ -88028,6 +88263,11 @@ function installInspector(win, properties) {
88028
88263
  disable();
88029
88264
  return;
88030
88265
  }
88266
+ if (data.action === "mode" && (data.mode === "select" || data.mode === "comment")) {
88267
+ mode = data.mode;
88268
+ cursor.textContent = `*{cursor:${mode === "comment" ? commentCursor : "default"}!important}`;
88269
+ return;
88270
+ }
88031
88271
  if (!enabled && data.action !== "reset") return;
88032
88272
  if (data.session !== session) return;
88033
88273
  if (data.action === "cancel-drag") {
@@ -88156,6 +88396,8 @@ var INSPECTOR_PROPERTIES = [
88156
88396
  "color",
88157
88397
  "box-shadow"
88158
88398
  ];
88399
+ var COMMENT_CURSOR_SVG = "<svg xmlns='http://www.w3.org/2000/svg' width='26' height='26' viewBox='0 0 26 26'><path d='M2 24V13a11 11 0 1 1 11 11z' fill='white' stroke='black' stroke-width='1.5' stroke-linejoin='round'/><path d='M13 8.5v9M8.5 13h9' stroke='black' stroke-width='1.8' stroke-linecap='round'/></svg>";
88400
+ var COMMENT_CURSOR = `url("data:image/svg+xml,${encodeURIComponent(COMMENT_CURSOR_SVG)}") 2 24, crosshair`;
88159
88401
 
88160
88402
  // src/server/inject.ts
88161
88403
  var RUNTIME = `(() => {
@@ -88266,7 +88508,7 @@ var EMBEDDED_STYLE = "<style>:root{color-scheme:light dark;background:transparen
88266
88508
  var UI_MARKER = /<ryugi-[a-z0-9-]+(?:\s|>)/i;
88267
88509
  var UI_META = /<meta[^>]+name=["']ryugi-ui["'][^>]*>/i;
88268
88510
  function injectRuntime(html, options = {}) {
88269
- const inspect = options.inspector ? `;(${installInspector.toString().replace(/\n/g, " ")})(window,${JSON.stringify(INSPECTOR_PROPERTIES)});` : "";
88511
+ const inspect = options.inspector ? `;(${installInspector.toString().replace(/\n/g, " ")})(window,${JSON.stringify(INSPECTOR_PROPERTIES)},${JSON.stringify(COMMENT_CURSOR)});` : "";
88270
88512
  const tag = `${options.embedded ? EMBEDDED_STYLE : ""}<script>${inspect || RUNTIME_LINE}</script>`;
88271
88513
  const ui2 = options.uiRuntimeUrl && !options.inspector && (UI_MARKER.test(html) || UI_META.test(html)) ? `<script defer src="${options.uiRuntimeUrl.replace(/"/g, "&quot;")}"></script>` : "";
88272
88514
  const head = HEAD.exec(html);
@@ -88275,9 +88517,9 @@ function injectRuntime(html, options = {}) {
88275
88517
  }
88276
88518
 
88277
88519
  // src/server/http.ts
88278
- var import_node_crypto2 = __toESM(require("node:crypto"), 1);
88279
- var import_node_fs9 = __toESM(require("node:fs"), 1);
88280
- var import_node_path9 = __toESM(require("node:path"), 1);
88520
+ var import_node_crypto3 = __toESM(require("node:crypto"), 1);
88521
+ var import_node_fs10 = __toESM(require("node:fs"), 1);
88522
+ var import_node_path10 = __toESM(require("node:path"), 1);
88281
88523
  var MIME2 = {
88282
88524
  ".html": "text/html; charset=utf-8",
88283
88525
  ".js": "text/javascript; charset=utf-8",
@@ -88300,12 +88542,12 @@ var MIME2 = {
88300
88542
  ".mp4": "video/mp4",
88301
88543
  ".webm": "video/webm"
88302
88544
  };
88303
- var mimeOf = (file) => MIME2[import_node_path9.default.extname(file).toLowerCase()] ?? "application/octet-stream";
88304
- var randomToken = () => import_node_crypto2.default.randomBytes(24).toString("base64url");
88545
+ var mimeOf = (file) => MIME2[import_node_path10.default.extname(file).toLowerCase()] ?? "application/octet-stream";
88546
+ var randomToken = () => import_node_crypto3.default.randomBytes(24).toString("base64url");
88305
88547
  function safeEqual(a5, b6) {
88306
88548
  const ab = Buffer.from(a5);
88307
88549
  const bb = Buffer.from(b6);
88308
- return ab.length === bb.length && import_node_crypto2.default.timingSafeEqual(ab, bb);
88550
+ return ab.length === bb.length && import_node_crypto3.default.timingSafeEqual(ab, bb);
88309
88551
  }
88310
88552
  function parseCookies(header) {
88311
88553
  const cookies = {};
@@ -88317,7 +88559,7 @@ function parseCookies(header) {
88317
88559
  }
88318
88560
  function readPersisted(file) {
88319
88561
  try {
88320
- return import_node_fs9.default.readFileSync(file, "utf8").trim();
88562
+ return import_node_fs10.default.readFileSync(file, "utf8").trim();
88321
88563
  } catch {
88322
88564
  return "";
88323
88565
  }
@@ -88346,15 +88588,15 @@ function sendJson(res, status, value) {
88346
88588
  send(res, status, JSON.stringify(value), { "Content-Type": MIME2[".json"] });
88347
88589
  }
88348
88590
  function sendFile(res, file, headers = {}) {
88349
- import_node_fs9.default.readFile(file, (err, data) => {
88591
+ import_node_fs10.default.readFile(file, (err, data) => {
88350
88592
  if (err) send(res, 404, "Not found");
88351
88593
  else send(res, 200, data, { "Content-Type": mimeOf(file), ...headers });
88352
88594
  });
88353
88595
  }
88354
88596
 
88355
88597
  // src/server/dynamic-blocks.ts
88356
- var import_node_fs10 = __toESM(require("node:fs"), 1);
88357
- var import_node_path10 = __toESM(require("node:path"), 1);
88598
+ var import_node_fs11 = __toESM(require("node:fs"), 1);
88599
+ var import_node_path11 = __toESM(require("node:path"), 1);
88358
88600
  function dynamicUrls(xml) {
88359
88601
  const urls = /* @__PURE__ */ new Set();
88360
88602
  for (const match of stripOpaque(xml).matchAll(TAG)) {
@@ -88367,7 +88609,7 @@ function dynamicUrls(xml) {
88367
88609
  }
88368
88610
  var mtimeOf = (file) => {
88369
88611
  try {
88370
- return import_node_fs10.default.statSync(file).mtimeMs;
88612
+ return import_node_fs11.default.statSync(file).mtimeMs;
88371
88613
  } catch {
88372
88614
  return -1;
88373
88615
  }
@@ -88384,7 +88626,7 @@ function createDynamicBlocks(limits) {
88384
88626
  };
88385
88627
  const entry = (id2, dir, file) => {
88386
88628
  const map = files(id2);
88387
- const mtime = mtimeOf(import_node_path10.default.join(dir, file));
88629
+ const mtime = mtimeOf(import_node_path11.default.join(dir, file));
88388
88630
  let state = map.get(file);
88389
88631
  if (!state || state.mtime !== mtime) {
88390
88632
  state = { mtime, errors: [], ran: false };
@@ -88409,19 +88651,19 @@ function createDynamicBlocks(limits) {
88409
88651
  state(id2, dir) {
88410
88652
  let xml = "";
88411
88653
  try {
88412
- xml = import_node_fs10.default.readFileSync(import_node_path10.default.join(dir, "doc.xml"), "utf8");
88654
+ xml = import_node_fs11.default.readFileSync(import_node_path11.default.join(dir, "doc.xml"), "utf8");
88413
88655
  } catch {
88414
88656
  return { warnings: [], dynamic: {} };
88415
88657
  }
88416
88658
  const urls = dynamicUrls(xml);
88417
88659
  const map = files(id2);
88418
88660
  for (const [file, state] of map) {
88419
- if (!urls.includes(file) || state.mtime !== mtimeOf(import_node_path10.default.join(dir, file))) map.delete(file);
88661
+ if (!urls.includes(file) || state.mtime !== mtimeOf(import_node_path11.default.join(dir, file))) map.delete(file);
88420
88662
  }
88421
88663
  const warnings = [];
88422
88664
  const dynamic = {};
88423
88665
  for (const url of urls) {
88424
- if (!import_node_fs10.default.existsSync(import_node_path10.default.join(dir, url))) warnings.push(`${url} not found`);
88666
+ if (!import_node_fs11.default.existsSync(import_node_path11.default.join(dir, url))) warnings.push(`${url} not found`);
88425
88667
  const state = map.get(url);
88426
88668
  for (const error of state?.errors ?? []) warnings.push(`runtime: ${url}: ${error}`);
88427
88669
  dynamic[url] = { ran: state?.ran ?? false };
@@ -88432,8 +88674,8 @@ function createDynamicBlocks(limits) {
88432
88674
  }
88433
88675
 
88434
88676
  // src/server/selection.ts
88435
- var import_node_fs11 = __toESM(require("node:fs"), 1);
88436
- var import_node_path11 = __toESM(require("node:path"), 1);
88677
+ var import_node_fs12 = __toESM(require("node:fs"), 1);
88678
+ var import_node_path12 = __toESM(require("node:path"), 1);
88437
88679
 
88438
88680
  // src/shared/selection.ts
88439
88681
  var SELECTION_FULL_NODES = 20;
@@ -88508,14 +88750,14 @@ function readAnySelection(value) {
88508
88750
  function createSelectionStore(stateDir, exists) {
88509
88751
  const tabs = /* @__PURE__ */ new Map();
88510
88752
  let clock = 0;
88511
- const fileOf = (kind, id2) => import_node_path11.default.join(stateDir, kind, id2, "selection.json");
88753
+ const fileOf = (kind, id2) => import_node_path12.default.join(stateDir, kind, id2, "selection.json");
88512
88754
  const write = (kind, id2) => {
88513
88755
  let winner;
88514
88756
  for (const tab of tabs.values()) {
88515
88757
  if (tab.kind !== kind || tab.id !== id2) continue;
88516
88758
  if (!winner || tab.humanAt > winner.humanAt || tab.humanAt === winner.humanAt && tab.seenAt > winner.seenAt) winner = tab;
88517
88759
  }
88518
- if (!winner?.state) import_node_fs11.default.rmSync(fileOf(kind, id2), { force: true });
88760
+ if (!winner?.state) import_node_fs12.default.rmSync(fileOf(kind, id2), { force: true });
88519
88761
  else writeJsonAtomic(fileOf(kind, id2), { artifact: `${kind}/${id2}`, updatedAt: (/* @__PURE__ */ new Date()).toISOString(), ...winner.state });
88520
88762
  };
88521
88763
  return {
@@ -88546,19 +88788,19 @@ function createSelectionStore(stateDir, exists) {
88546
88788
  for (const kind of ["artboard", "document"]) {
88547
88789
  let ids = [];
88548
88790
  try {
88549
- ids = import_node_fs11.default.readdirSync(import_node_path11.default.join(stateDir, kind));
88791
+ ids = import_node_fs12.default.readdirSync(import_node_path12.default.join(stateDir, kind));
88550
88792
  } catch {
88551
88793
  continue;
88552
88794
  }
88553
- for (const id2 of ids) import_node_fs11.default.rmSync(fileOf(kind, id2), { force: true });
88795
+ for (const id2 of ids) import_node_fs12.default.rmSync(fileOf(kind, id2), { force: true });
88554
88796
  }
88555
88797
  }
88556
88798
  };
88557
88799
  }
88558
88800
 
88559
88801
  // src/server/config.ts
88560
- var import_node_fs12 = __toESM(require("node:fs"), 1);
88561
- var import_node_path12 = __toESM(require("node:path"), 1);
88802
+ var import_node_fs13 = __toESM(require("node:fs"), 1);
88803
+ var import_node_path13 = __toESM(require("node:path"), 1);
88562
88804
 
88563
88805
  // src/shared/cloud.ts
88564
88806
  var ownLink = (url, hostId) => hostId ? `${url}#ryugi-host=${hostId}` : url;
@@ -88593,7 +88835,7 @@ function normalize(raw) {
88593
88835
  function readConfig(file) {
88594
88836
  let text2;
88595
88837
  try {
88596
- text2 = import_node_fs12.default.readFileSync(file, "utf8");
88838
+ text2 = import_node_fs13.default.readFileSync(file, "utf8");
88597
88839
  } catch {
88598
88840
  return { config: defaults() };
88599
88841
  }
@@ -88604,22 +88846,22 @@ function readConfig(file) {
88604
88846
  }
88605
88847
  }
88606
88848
  function ensureGitignore(dir) {
88607
- const file = import_node_path12.default.join(dir, ".gitignore");
88849
+ const file = import_node_path13.default.join(dir, ".gitignore");
88608
88850
  let current = "";
88609
88851
  try {
88610
- current = import_node_fs12.default.readFileSync(file, "utf8");
88852
+ current = import_node_fs13.default.readFileSync(file, "utf8");
88611
88853
  } catch {
88612
- import_node_fs12.default.writeFileSync(file, "config.json\n");
88854
+ import_node_fs13.default.writeFileSync(file, "config.json\n");
88613
88855
  return;
88614
88856
  }
88615
88857
  if (current.split(/\r?\n/).some((line) => line.trim() === "config.json")) return;
88616
- import_node_fs12.default.writeFileSync(file, `${current}${current === "" || current.endsWith("\n") ? "" : "\n"}config.json
88858
+ import_node_fs13.default.writeFileSync(file, `${current}${current === "" || current.endsWith("\n") ? "" : "\n"}config.json
88617
88859
  `);
88618
88860
  }
88619
88861
  function ensureConfig(file) {
88620
- import_node_fs12.default.mkdirSync(import_node_path12.default.dirname(file), { recursive: true });
88621
- ensureGitignore(import_node_path12.default.dirname(file));
88622
- if (!import_node_fs12.default.existsSync(file)) writeJsonAtomic(file, defaults());
88862
+ import_node_fs13.default.mkdirSync(import_node_path13.default.dirname(file), { recursive: true });
88863
+ ensureGitignore(import_node_path13.default.dirname(file));
88864
+ if (!import_node_fs13.default.existsSync(file)) writeJsonAtomic(file, defaults());
88623
88865
  }
88624
88866
  function updateArtifactConfig(file, key2, patch) {
88625
88867
  const { config, warning } = readConfig(file);
@@ -88639,21 +88881,21 @@ function setProjectAutoPublish(file, value) {
88639
88881
  if (warning) throw new Error(warning);
88640
88882
  if (value === null) delete config.autoPublish;
88641
88883
  else config.autoPublish = value;
88642
- import_node_fs12.default.mkdirSync(import_node_path12.default.dirname(file), { recursive: true });
88884
+ import_node_fs13.default.mkdirSync(import_node_path13.default.dirname(file), { recursive: true });
88643
88885
  writeJsonAtomic(file, config);
88644
88886
  }
88645
88887
 
88646
88888
  // src/server/credentials.ts
88647
- var import_node_fs13 = __toESM(require("node:fs"), 1);
88648
- var import_node_path13 = __toESM(require("node:path"), 1);
88889
+ var import_node_fs14 = __toESM(require("node:fs"), 1);
88890
+ var import_node_path14 = __toESM(require("node:path"), 1);
88649
88891
  function credentialsFile(env, platform, home) {
88650
- if (platform === "win32" && env.APPDATA) return import_node_path13.default.join(env.APPDATA, "ryugi", "credentials.json");
88651
- return import_node_path13.default.join(env.XDG_CONFIG_HOME || import_node_path13.default.join(home, ".config"), "ryugi", "credentials.json");
88892
+ if (platform === "win32" && env.APPDATA) return import_node_path14.default.join(env.APPDATA, "ryugi", "credentials.json");
88893
+ return import_node_path14.default.join(env.XDG_CONFIG_HOME || import_node_path14.default.join(home, ".config"), "ryugi", "credentials.json");
88652
88894
  }
88653
88895
  function readCredentials(file, origin) {
88654
88896
  let raw;
88655
88897
  try {
88656
- raw = JSON.parse(import_node_fs13.default.readFileSync(file, "utf8"));
88898
+ raw = JSON.parse(import_node_fs14.default.readFileSync(file, "utf8"));
88657
88899
  } catch {
88658
88900
  return null;
88659
88901
  }
@@ -88664,11 +88906,11 @@ function readCredentials(file, origin) {
88664
88906
  return { origin: savedOrigin, token, login };
88665
88907
  }
88666
88908
  function writeCredentials(file, credentials) {
88667
- import_node_fs13.default.mkdirSync(import_node_path13.default.dirname(file), { recursive: true, mode: 448 });
88909
+ import_node_fs14.default.mkdirSync(import_node_path14.default.dirname(file), { recursive: true, mode: 448 });
88668
88910
  writeJsonAtomic(file, credentials);
88669
88911
  }
88670
88912
  function clearCredentials(file) {
88671
- import_node_fs13.default.rmSync(file, { force: true });
88913
+ import_node_fs14.default.rmSync(file, { force: true });
88672
88914
  }
88673
88915
 
88674
88916
  // node_modules/.pnpm/ws@8.18.3/node_modules/ws/wrapper.mjs
@@ -88796,7 +89038,7 @@ function createRelayClient(options) {
88796
89038
  }
88797
89039
 
88798
89040
  // src/server/ws.ts
88799
- var import_node_crypto3 = __toESM(require("node:crypto"), 1);
89041
+ var import_node_crypto4 = __toESM(require("node:crypto"), 1);
88800
89042
  var WS_MAGIC = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
88801
89043
  var OPCODE_TEXT = 1;
88802
89044
  var OPCODE_CLOSE = 8;
@@ -88804,7 +89046,7 @@ var OPCODE_PING = 9;
88804
89046
  var OPCODE_PONG = 10;
88805
89047
  var MAX_CLIENT_FRAME = 8 * 1024 * 1024;
88806
89048
  function acceptKey(clientKey) {
88807
- return import_node_crypto3.default.createHash("sha1").update(clientKey + WS_MAGIC).digest("base64");
89049
+ return import_node_crypto4.default.createHash("sha1").update(clientKey + WS_MAGIC).digest("base64");
88808
89050
  }
88809
89051
  function encodeFrame(opcode, payload) {
88810
89052
  const len = payload.length;
@@ -88929,21 +89171,21 @@ function createDirectRelay(options) {
88929
89171
  }
88930
89172
 
88931
89173
  // src/server/host-id.ts
88932
- var import_node_fs14 = __toESM(require("node:fs"), 1);
88933
- var import_node_path14 = __toESM(require("node:path"), 1);
89174
+ var import_node_fs15 = __toESM(require("node:fs"), 1);
89175
+ var import_node_path15 = __toESM(require("node:path"), 1);
88934
89176
  function hostIdFor(credentialsFile2) {
88935
- const file = import_node_path14.default.join(import_node_path14.default.dirname(credentialsFile2), "host-id");
89177
+ const file = import_node_path15.default.join(import_node_path15.default.dirname(credentialsFile2), "host-id");
88936
89178
  const saved = readPersisted(file);
88937
89179
  if (/^[A-Za-z0-9_-]{16,64}$/.test(saved)) return saved;
88938
89180
  const id2 = randomToken();
88939
- import_node_fs14.default.mkdirSync(import_node_path14.default.dirname(file), { recursive: true, mode: 448 });
88940
- import_node_fs14.default.writeFileSync(file, `${id2}
89181
+ import_node_fs15.default.mkdirSync(import_node_path15.default.dirname(file), { recursive: true, mode: 448 });
89182
+ import_node_fs15.default.writeFileSync(file, `${id2}
88941
89183
  `, { mode: 384 });
88942
89184
  return id2;
88943
89185
  }
88944
89186
 
88945
89187
  // src/server/avatars.ts
88946
- var import_node_crypto4 = require("node:crypto");
89188
+ var import_node_crypto5 = require("node:crypto");
88947
89189
  var AVATAR_ROUTE = /^\/api\/avatar\/([a-z]+)\/([^/]+)$/;
88948
89190
  var MAX_BYTES = 512 * 1024;
88949
89191
  var TIMEOUT_MS = 5e3;
@@ -88952,7 +89194,7 @@ var SOURCES = {
88952
89194
  gravatar: { valid: /^[0-9a-f]{64}$/, url: (id2) => `https://gravatar.com/avatar/${id2}?s=64&d=404` }
88953
89195
  };
88954
89196
  var githubAvatar = (login) => `/api/avatar/github/${login}`;
88955
- var gravatarAvatar = (email) => `/api/avatar/gravatar/${(0, import_node_crypto4.createHash)("sha256").update(email.trim().toLowerCase()).digest("hex")}`;
89197
+ var gravatarAvatar = (email) => `/api/avatar/gravatar/${(0, import_node_crypto5.createHash)("sha256").update(email.trim().toLowerCase()).digest("hex")}`;
88956
89198
  function createAvatars(fetchImpl = fetch) {
88957
89199
  const cache = /* @__PURE__ */ new Map();
88958
89200
  const fetchAvatar = async (url) => {
@@ -88984,7 +89226,7 @@ function createAvatars(fetchImpl = fetch) {
88984
89226
  }
88985
89227
 
88986
89228
  // src/server/cloud.ts
88987
- var import_node_crypto5 = __toESM(require("node:crypto"), 1);
89229
+ var import_node_crypto6 = __toESM(require("node:crypto"), 1);
88988
89230
 
88989
89231
  // src/shared/brand.ts
88990
89232
  var RYUGI_MARK_VIEWBOX = "0 0 512 512";
@@ -89094,32 +89336,32 @@ function callbackPage(result) {
89094
89336
  }
89095
89337
 
89096
89338
  // src/server/pack.ts
89097
- var import_node_fs15 = __toESM(require("node:fs"), 1);
89098
- var import_node_path15 = __toESM(require("node:path"), 1);
89339
+ var import_node_fs16 = __toESM(require("node:fs"), 1);
89340
+ var import_node_path16 = __toESM(require("node:path"), 1);
89099
89341
  var MAX_ARTIFACT_BYTES = 20 * 1024 * 1024;
89100
89342
  function formatBytes(bytes) {
89101
89343
  if (bytes < 1024) return `${bytes} B`;
89102
89344
  if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
89103
89345
  return `${(bytes / 1024 / 1024).toFixed(1)} MB`;
89104
89346
  }
89105
- var excluded = (type, relative) => type === "document" && relative === "comments.json";
89347
+ var excluded = (type, relative) => (type === "document" || type === "artboard") && relative === "comments.json";
89106
89348
  function packArtifact(dir, type, { maxBytes = MAX_ARTIFACT_BYTES } = {}) {
89107
- const rootReal = import_node_fs15.default.realpathSync(dir);
89349
+ const rootReal = import_node_fs16.default.realpathSync(dir);
89108
89350
  const found = [];
89109
89351
  const visited = /* @__PURE__ */ new Set([rootReal]);
89110
89352
  let total = 0;
89111
89353
  const walk = (current) => {
89112
- for (const entry of import_node_fs15.default.readdirSync(current, { withFileTypes: true })) {
89354
+ for (const entry of import_node_fs16.default.readdirSync(current, { withFileTypes: true })) {
89113
89355
  if (entry.name.startsWith(".")) continue;
89114
- const full = import_node_path15.default.join(current, entry.name);
89356
+ const full = import_node_path16.default.join(current, entry.name);
89115
89357
  let real;
89116
89358
  try {
89117
- real = import_node_fs15.default.realpathSync(full);
89359
+ real = import_node_fs16.default.realpathSync(full);
89118
89360
  } catch {
89119
89361
  continue;
89120
89362
  }
89121
- if (!real.startsWith(rootReal + import_node_path15.default.sep)) continue;
89122
- const stat = import_node_fs15.default.statSync(real);
89363
+ if (!real.startsWith(rootReal + import_node_path16.default.sep)) continue;
89364
+ const stat = import_node_fs16.default.statSync(real);
89123
89365
  if (stat.isDirectory()) {
89124
89366
  if (visited.has(real)) continue;
89125
89367
  visited.add(real);
@@ -89127,7 +89369,7 @@ function packArtifact(dir, type, { maxBytes = MAX_ARTIFACT_BYTES } = {}) {
89127
89369
  continue;
89128
89370
  }
89129
89371
  if (!stat.isFile()) continue;
89130
- const relative = import_node_path15.default.relative(dir, full).split(import_node_path15.default.sep).join("/");
89372
+ const relative = import_node_path16.default.relative(dir, full).split(import_node_path16.default.sep).join("/");
89131
89373
  if (excluded(type, relative)) continue;
89132
89374
  total += stat.size;
89133
89375
  found.push({ path: relative, real });
@@ -89137,12 +89379,12 @@ function packArtifact(dir, type, { maxBytes = MAX_ARTIFACT_BYTES } = {}) {
89137
89379
  if (total > maxBytes) {
89138
89380
  throw new Error(`artifact is ${formatBytes(total)}; the limit is ${formatBytes(maxBytes)}`);
89139
89381
  }
89140
- return found.map((file) => ({ path: file.path, data: import_node_fs15.default.readFileSync(file.real) }));
89382
+ return found.map((file) => ({ path: file.path, data: import_node_fs16.default.readFileSync(file.real) }));
89141
89383
  }
89142
89384
 
89143
89385
  // src/server/publish-payload.ts
89144
- var import_node_fs16 = __toESM(require("node:fs"), 1);
89145
- var import_node_path16 = __toESM(require("node:path"), 1);
89386
+ var import_node_fs17 = __toESM(require("node:fs"), 1);
89387
+ var import_node_path17 = __toESM(require("node:path"), 1);
89146
89388
  var CLOUD_RUNTIME_URL = "/_ryugi/runtime/ryugi-ui.js";
89147
89389
  function sharedComments(file) {
89148
89390
  return {
@@ -89155,7 +89397,7 @@ function publishPayload(ref, dir, title, payload, comments = emptyComments()) {
89155
89397
  const { canvas, layout, versions } = payload;
89156
89398
  return { canvas, layout, versions };
89157
89399
  }
89158
- if (ref.type === "document") return { title, state: parseDocument2(import_node_fs16.default.readFileSync(import_node_path16.default.join(dir, "doc.xml"), "utf8")), comments: sharedComments(comments) };
89400
+ if (ref.type === "document") return { title, state: parseDocument2(import_node_fs17.default.readFileSync(import_node_path17.default.join(dir, "doc.xml"), "utf8")), comments: sharedComments(comments) };
89159
89401
  return { title };
89160
89402
  }
89161
89403
  var isPage = (type, file) => type === "html" ? file === "index.html" : (type === "document" || type === "artboard") && file.endsWith(".html");
@@ -89166,12 +89408,12 @@ function withCloudRuntime(type, files) {
89166
89408
  }
89167
89409
 
89168
89410
  // src/server/settings.ts
89169
- var import_node_fs17 = __toESM(require("node:fs"), 1);
89170
- var import_node_path17 = __toESM(require("node:path"), 1);
89171
- var settingsFileFor = (credentialsFile2) => import_node_path17.default.join(import_node_path17.default.dirname(credentialsFile2), "config.json");
89411
+ var import_node_fs18 = __toESM(require("node:fs"), 1);
89412
+ var import_node_path18 = __toESM(require("node:path"), 1);
89413
+ var settingsFileFor = (credentialsFile2) => import_node_path18.default.join(import_node_path18.default.dirname(credentialsFile2), "config.json");
89172
89414
  function readSettings(file) {
89173
89415
  try {
89174
- const raw = JSON.parse(import_node_fs17.default.readFileSync(file, "utf8"));
89416
+ const raw = JSON.parse(import_node_fs18.default.readFileSync(file, "utf8"));
89175
89417
  return typeof raw === "object" && raw !== null && !Array.isArray(raw) ? raw : {};
89176
89418
  } catch {
89177
89419
  return {};
@@ -89182,7 +89424,7 @@ function readGlobalAutoPublish(file) {
89182
89424
  return typeof value === "boolean" ? value : void 0;
89183
89425
  }
89184
89426
  function writeGlobalAutoPublish(file, value) {
89185
- import_node_fs17.default.mkdirSync(import_node_path17.default.dirname(file), { recursive: true, mode: 448 });
89427
+ import_node_fs18.default.mkdirSync(import_node_path18.default.dirname(file), { recursive: true, mode: 448 });
89186
89428
  writeJsonAtomic(file, { ...readSettings(file), autoPublish: value });
89187
89429
  }
89188
89430
  function defaultGlobalAutoPublish(file) {
@@ -89254,10 +89496,10 @@ function createCloudRoutes(options) {
89254
89496
  const state = randomToken();
89255
89497
  const verifier = randomToken();
89256
89498
  pendingStates.set(state, { expires: now + LOGIN_STATE_TTL_MS, verifier });
89257
- const challenge = import_node_crypto5.default.createHash("sha256").update(verifier).digest("base64url");
89499
+ const challenge = import_node_crypto6.default.createHash("sha256").update(verifier).digest("base64url");
89258
89500
  return `${origin}/auth/cli?port=${options.port()}&state=${state}&challenge=${challenge}`;
89259
89501
  };
89260
- const cloudFetch = async (method, path21, body) => {
89502
+ const cloudFetch = async (method, path22, body) => {
89261
89503
  const credentials = readCredentials(credentialsFile2, origin);
89262
89504
  if (!credentials) throw new SignedOut();
89263
89505
  const headers = { Authorization: `Bearer ${credentials.token}` };
@@ -89271,7 +89513,7 @@ function createCloudRoutes(options) {
89271
89513
  }
89272
89514
  let res;
89273
89515
  try {
89274
- res = await fetch(`${origin}${path21}`, { method, headers, body: payload });
89516
+ res = await fetch(`${origin}${path22}`, { method, headers, body: payload });
89275
89517
  } catch (err) {
89276
89518
  throw new CloudRefused(502, `could not reach ${origin}: ${err.message}`);
89277
89519
  }
@@ -89328,9 +89570,9 @@ function createCloudRoutes(options) {
89328
89570
  }
89329
89571
  const key2 = `${ref.type}/${ref.id}`;
89330
89572
  const entry = artifactEntry(key2);
89331
- const hashed = files.map((file) => ({ ...file, hash: import_node_crypto5.default.createHash("sha256").update(file.data).digest("hex") }));
89573
+ const hashed = files.map((file) => ({ ...file, hash: import_node_crypto6.default.createHash("sha256").update(file.data).digest("hex") }));
89332
89574
  const byHash = new Map(hashed.map((file) => [file.hash, file.data]));
89333
- const manifest = hashed.map(({ path: path21, hash }) => ({ path: path21, hash }));
89575
+ const manifest = hashed.map(({ path: path22, hash }) => ({ path: path22, hash }));
89334
89576
  const version2 = (visibility) => ({ title: inspected.title, payload: inspected.payload, files: manifest, project, ...visibility ? { visibility } : {} });
89335
89577
  const create3 = () => createRecord2(ref, requested ?? entry.visibility ?? "private");
89336
89578
  const upload = async (id2, hashes) => {
@@ -89553,7 +89795,7 @@ function createCloudRoutes(options) {
89553
89795
 
89554
89796
  // src/server/comment-names.ts
89555
89797
  var import_node_child_process = require("node:child_process");
89556
- var import_node_fs18 = __toESM(require("node:fs"), 1);
89798
+ var import_node_fs19 = __toESM(require("node:fs"), 1);
89557
89799
 
89558
89800
  // src/server/agent.ts
89559
89801
  var NAMES = {
@@ -89634,7 +89876,7 @@ function commentNames({ agentFile, projectDir, cloud }) {
89634
89876
  },
89635
89877
  agent: () => {
89636
89878
  try {
89637
- return detectAgent(JSON.parse(import_node_fs18.default.readFileSync(agentFile, "utf8")));
89879
+ return detectAgent(JSON.parse(import_node_fs19.default.readFileSync(agentFile, "utf8")));
89638
89880
  } catch {
89639
89881
  return void 0;
89640
89882
  }
@@ -89662,8 +89904,8 @@ function viewerPage(template, page) {
89662
89904
  }
89663
89905
 
89664
89906
  // src/server/auto-publish.ts
89665
- var import_node_crypto6 = __toESM(require("node:crypto"), 1);
89666
- var import_node_fs19 = __toESM(require("node:fs"), 1);
89907
+ var import_node_crypto7 = __toESM(require("node:crypto"), 1);
89908
+ var import_node_fs20 = __toESM(require("node:fs"), 1);
89667
89909
  var SIGNED_OUT_MESSAGE = "auto-publish paused: the Ryugi Cloud sign-in expired";
89668
89910
  function isNotice(value) {
89669
89911
  if (typeof value !== "object" || value === null) return false;
@@ -89673,7 +89915,7 @@ function isNotice(value) {
89673
89915
  function readAutoPublishState(file) {
89674
89916
  let raw = {};
89675
89917
  try {
89676
- const parsed = JSON.parse(import_node_fs19.default.readFileSync(file, "utf8"));
89918
+ const parsed = JSON.parse(import_node_fs20.default.readFileSync(file, "utf8"));
89677
89919
  if (typeof parsed === "object" && parsed !== null) raw = parsed;
89678
89920
  } catch {
89679
89921
  }
@@ -89693,7 +89935,7 @@ function createAutoPublisher(options) {
89693
89935
  const remarked = /* @__PURE__ */ new Set();
89694
89936
  const save = () => writeJsonAtomic(options.stateFile, { dirty: [...dirty], notice, paused });
89695
89937
  const raise = (kind, message) => {
89696
- if (notice?.kind !== kind) notice = { id: import_node_crypto6.default.randomUUID(), kind, message };
89938
+ if (notice?.kind !== kind) notice = { id: import_node_crypto7.default.randomUUID(), kind, message };
89697
89939
  };
89698
89940
  const run2 = async () => {
89699
89941
  if (!options.enabled() || dirty.size === 0) return;
@@ -89753,7 +89995,7 @@ function createAutoPublisher(options) {
89753
89995
  }
89754
89996
 
89755
89997
  // src/server/artifact-file.ts
89756
- var import_node_fs20 = __toESM(require("node:fs"), 1);
89998
+ var import_node_fs21 = __toESM(require("node:fs"), 1);
89757
89999
  var RESOLVE = { artboard: resolveBoardFile, document: resolveDocumentAsset, html: resolveHtmlFile };
89758
90000
  var isArtifactPage = (type, relative) => type === "html" ? relative === "index.html" : relative.endsWith(".html");
89759
90001
  function readArtifactFile(dir, type, relative, uiRuntimeUrl) {
@@ -89762,7 +90004,7 @@ function readArtifactFile(dir, type, relative, uiRuntimeUrl) {
89762
90004
  if (!target) return null;
89763
90005
  let data;
89764
90006
  try {
89765
- data = import_node_fs20.default.readFileSync(target);
90007
+ data = import_node_fs21.default.readFileSync(target);
89766
90008
  } catch {
89767
90009
  return null;
89768
90010
  }
@@ -89772,25 +90014,25 @@ function readArtifactFile(dir, type, relative, uiRuntimeUrl) {
89772
90014
  }
89773
90015
 
89774
90016
  // src/server/watch.ts
89775
- var import_node_fs21 = __toESM(require("node:fs"), 1);
89776
- var import_node_path18 = __toESM(require("node:path"), 1);
89777
- var toPosix = (file) => file.split(import_node_path18.default.sep).join("/");
90017
+ var import_node_fs22 = __toESM(require("node:fs"), 1);
90018
+ var import_node_path19 = __toESM(require("node:path"), 1);
90019
+ var toPosix = (file) => file.split(import_node_path19.default.sep).join("/");
89778
90020
  var ignored = (file) => file === ".state" || file.startsWith(".state/");
89779
90021
  function snapshot(root, dir = root, out = /* @__PURE__ */ new Map()) {
89780
90022
  let entries2;
89781
90023
  try {
89782
- entries2 = import_node_fs21.default.readdirSync(dir, { withFileTypes: true });
90024
+ entries2 = import_node_fs22.default.readdirSync(dir, { withFileTypes: true });
89783
90025
  } catch {
89784
90026
  return out;
89785
90027
  }
89786
90028
  for (const entry of entries2) {
89787
- const full = import_node_path18.default.join(dir, entry.name);
89788
- const rel = toPosix(import_node_path18.default.relative(root, full));
90029
+ const full = import_node_path19.default.join(dir, entry.name);
90030
+ const rel = toPosix(import_node_path19.default.relative(root, full));
89789
90031
  if (ignored(rel)) continue;
89790
90032
  if (entry.isDirectory()) snapshot(root, full, out);
89791
90033
  else {
89792
90034
  try {
89793
- out.set(rel, import_node_fs21.default.statSync(full).mtimeMs);
90035
+ out.set(rel, import_node_fs22.default.statSync(full).mtimeMs);
89794
90036
  } catch {
89795
90037
  }
89796
90038
  }
@@ -89823,7 +90065,7 @@ function watchTree(root, onChange, debounceMs = 120, backupPollMs = 1e3) {
89823
90065
  }, debounceMs);
89824
90066
  };
89825
90067
  try {
89826
- const watcher = import_node_fs21.default.watch(root, { recursive: true }, (_event, filename) => {
90068
+ const watcher = import_node_fs22.default.watch(root, { recursive: true }, (_event, filename) => {
89827
90069
  if (filename) queue([toPosix(filename.toString())]);
89828
90070
  });
89829
90071
  watcher.on("error", () => {
@@ -89873,6 +90115,8 @@ var API_PREVIEW_ROUTE = /^\/api\/preview\/([^/]+)\/([^/]+)$/;
89873
90115
  var API_DOCUMENT_ROUTE = /^\/api\/document\/([^/]+)\/(state|update|comments)$/;
89874
90116
  var MAX_COMMENT_OP = 64 * 1024;
89875
90117
  var API_AGENT_COMMENTS_ROUTE = /^\/api\/agent\/document\/([^/]+)\/comments$/;
90118
+ var API_BOARD_COMMENTS_ROUTE = /^\/api\/artboard\/([^/]+)\/comments$/;
90119
+ var API_AGENT_BOARD_COMMENTS_ROUTE = /^\/api\/agent\/artboard\/([^/]+)\/comments$/;
89876
90120
  var MAX_DOCUMENT_UPDATE = 8 * 1024 * 1024;
89877
90121
  var MAX_RELAY_FILE = 8 * 1024 * 1024;
89878
90122
  var MAX_RUNTIME_MESSAGE = 2048;
@@ -89891,17 +90135,17 @@ function titleOf(payload, ref) {
89891
90135
  return payload.canvas?.title || ref.id;
89892
90136
  }
89893
90137
  async function startArtifactsServer(options) {
89894
- const root = import_node_path19.default.resolve(options.root);
89895
- const viewerDir = import_node_path19.default.resolve(options.viewerDir);
89896
- const runtimeDir = options.runtimeDir ? import_node_path19.default.resolve(options.runtimeDir) : null;
89897
- const stateDir = import_node_path19.default.join(root, ".state");
89898
- import_node_fs22.default.mkdirSync(stateDir, { recursive: true, mode: 448 });
89899
- import_node_fs22.default.writeFileSync(import_node_path19.default.join(stateDir, ".gitignore"), "*\n");
90138
+ const root = import_node_path20.default.resolve(options.root);
90139
+ const viewerDir = import_node_path20.default.resolve(options.viewerDir);
90140
+ const runtimeDir = options.runtimeDir ? import_node_path20.default.resolve(options.runtimeDir) : null;
90141
+ const stateDir = import_node_path20.default.join(root, ".state");
90142
+ import_node_fs23.default.mkdirSync(stateDir, { recursive: true, mode: 448 });
90143
+ import_node_fs23.default.writeFileSync(import_node_path20.default.join(stateDir, ".gitignore"), "*\n");
89900
90144
  if (options.configFile) ensureConfig(options.configFile);
89901
- const portFile = import_node_path19.default.join(stateDir, ".last-port");
89902
- const tokenFile = import_node_path19.default.join(stateDir, ".last-token");
90145
+ const portFile = import_node_path20.default.join(stateDir, ".last-port");
90146
+ const tokenFile = import_node_path20.default.join(stateDir, ".last-token");
89903
90147
  const token = readPersisted(tokenFile) || randomToken();
89904
- import_node_fs22.default.writeFileSync(tokenFile, `${token}
90148
+ import_node_fs23.default.writeFileSync(tokenFile, `${token}
89905
90149
  `, { mode: 384 });
89906
90150
  const contentToken = randomToken();
89907
90151
  const runtimeWarnings = /* @__PURE__ */ new Map();
@@ -89931,7 +90175,8 @@ async function startArtifactsServer(options) {
89931
90175
  direct.emit(message.artifact, message);
89932
90176
  };
89933
90177
  const avatars = createAvatars(options.fetch);
89934
- const names = commentNames({ agentFile: import_node_path19.default.join(stateDir, "agent"), projectDir: import_node_path19.default.resolve(root, "../.."), cloud: options.cloud });
90178
+ const names = commentNames({ agentFile: import_node_path20.default.join(stateDir, "agent"), projectDir: import_node_path20.default.resolve(root, "../.."), cloud: options.cloud });
90179
+ const boardComments = createBoardCommentRegistry({ root, stateDir, broadcast, names });
89935
90180
  const documents = createDocumentRegistry({
89936
90181
  root,
89937
90182
  stateDir,
@@ -89969,8 +90214,8 @@ async function startArtifactsServer(options) {
89969
90214
  }
89970
90215
  return { dir, title, reasons, payload: published };
89971
90216
  };
89972
- const cloud = options.cloud && options.configFile ? createCloudRoutes({ ...options.cloud, configFile: options.configFile, project: import_node_path19.default.basename(import_node_path19.default.resolve(root, "../..")), port: () => port, inspect }) : null;
89973
- const autoPublisher = cloud ? createAutoPublisher({ stateFile: import_node_path19.default.join(stateDir, "auto-publish.json"), enabled: cloud.autoPublishEnabled, token: cloud.token, publish: cloud.autoPublish }) : null;
90217
+ const cloud = options.cloud && options.configFile ? createCloudRoutes({ ...options.cloud, configFile: options.configFile, project: import_node_path20.default.basename(import_node_path20.default.resolve(root, "../..")), port: () => port, inspect }) : null;
90218
+ const autoPublisher = cloud ? createAutoPublisher({ stateFile: import_node_path20.default.join(stateDir, "auto-publish.json"), enabled: cloud.autoPublishEnabled, token: cloud.token, publish: cloud.autoPublish }) : null;
89974
90219
  const sameOrigin = (req) => {
89975
90220
  const origin2 = req.headers.origin;
89976
90221
  const host = req.headers.host ?? "";
@@ -90049,7 +90294,7 @@ async function startArtifactsServer(options) {
90049
90294
  };
90050
90295
  const blockIdsOf = async (id2, dir) => {
90051
90296
  await (await documents.open(id2))?.flush();
90052
- const state = parseDocument2(import_node_fs22.default.readFileSync(import_node_path19.default.join(dir, "doc.xml"), "utf8"));
90297
+ const state = parseDocument2(import_node_fs23.default.readFileSync(import_node_path20.default.join(dir, "doc.xml"), "utf8"));
90053
90298
  return new Set(state.root.children.flatMap((block) => block.$?.blockId ? [block.$.blockId] : []));
90054
90299
  };
90055
90300
  const serveAgentComments = async (req, res, id2) => {
@@ -90089,6 +90334,55 @@ async function startArtifactsServer(options) {
90089
90334
  sendJson(res, 400, { error: err.message });
90090
90335
  }
90091
90336
  };
90337
+ const serveBoardComments = async (req, res, id2) => {
90338
+ if (req.method !== "POST" || !sameOrigin(req)) {
90339
+ send(res, 403, "Forbidden");
90340
+ return;
90341
+ }
90342
+ if (!resolveArtifactDir(root, "artboard", id2)) {
90343
+ sendJson(res, 404, { error: `artboard/${id2} not found` });
90344
+ return;
90345
+ }
90346
+ const op = asBoardHumanOp(await readCommentBody(req));
90347
+ if (!op) {
90348
+ sendJson(res, 400, { error: "not a comment op" });
90349
+ return;
90350
+ }
90351
+ sendJson(res, 200, boardComments.apply(id2, op));
90352
+ };
90353
+ const serveAgentBoardComments = async (req, res, id2) => {
90354
+ const dir = resolveArtifactDir(root, "artboard", id2);
90355
+ if (!dir) {
90356
+ sendJson(res, 404, { error: `artboard/${id2} not found` });
90357
+ return;
90358
+ }
90359
+ const body = await readCommentBody(req);
90360
+ const fields = typeof body === "object" && body !== null ? body : {};
90361
+ const agent = typeof fields.agent === "string" && fields.agent.length > 0 && fields.agent.length <= 100 ? fields.agent : void 0;
90362
+ if (fields.op === "list") {
90363
+ sendJson(res, 200, { type: "comments", artifact: `artboard/${id2}`, threads: boardComments.list(id2, agent ?? "Agent", fields.all === true) });
90364
+ return;
90365
+ }
90366
+ const shorthand = agentAddOnBoard(body, boardSpecs(dir));
90367
+ if (shorthand && "error" in shorthand) {
90368
+ sendJson(res, 400, { error: `${shorthand.error} in artboard/${id2}` });
90369
+ return;
90370
+ }
90371
+ const op = shorthand ?? asBoardAgentOp(body);
90372
+ if (!op) {
90373
+ sendJson(res, 400, { error: "not an agent comment op: use list, add {anchor: {page, board, x, y}, text}, reply {threadId, text} or resolve {threadId}; text must not be empty" });
90374
+ return;
90375
+ }
90376
+ if (op.op === "add" && op.anchor.board && !(op.anchor.board in boardFiles(dir))) {
90377
+ sendJson(res, 400, { error: `no board ${op.anchor.board} in artboard/${id2}` });
90378
+ return;
90379
+ }
90380
+ try {
90381
+ sendJson(res, 200, boardComments.applyAgent(id2, op, agent));
90382
+ } catch (err) {
90383
+ sendJson(res, 400, { error: err.message });
90384
+ }
90385
+ };
90092
90386
  const isAuthorized = (req, keyFromQuery) => keyFromQuery !== null && safeEqual(keyFromQuery, token) || safeEqual(parseCookies(req.headers.cookie)[cookieName] ?? "", token);
90093
90387
  const serveContent = (res, rest) => {
90094
90388
  if (rest.startsWith("runtime/")) {
@@ -90129,7 +90423,7 @@ async function startArtifactsServer(options) {
90129
90423
  sendFile(res, target, headers);
90130
90424
  return;
90131
90425
  }
90132
- import_node_fs22.default.readFile(target, "utf8", (err, source) => {
90426
+ import_node_fs23.default.readFile(target, "utf8", (err, source) => {
90133
90427
  if (err) send(res, 404, "Not found");
90134
90428
  else {
90135
90429
  const uiRuntimeUrl = runtimeDir ? `${contentPrefix}runtime/ryugi-ui.js` : void 0;
@@ -90140,6 +90434,7 @@ async function startArtifactsServer(options) {
90140
90434
  const artifactMeta = (ref, dir, contentOrigin = "") => ({
90141
90435
  ...readArtifact(dir, ref, runtimeOf(ref, dir)),
90142
90436
  ...ref.type !== "html" ? { agent: names.agent() } : {},
90437
+ ...ref.type === "artboard" ? { comments: boardComments.comments(ref.id), staleThreads: boardComments.stale(ref.id) } : {},
90143
90438
  contentBase: `${contentOrigin}${contentPrefix}${ref.type}/${ref.id}/`
90144
90439
  });
90145
90440
  const serveArtifactApi = (res, type, id2) => {
@@ -90158,7 +90453,7 @@ async function startArtifactsServer(options) {
90158
90453
  title: titleOf(readArtifact(entry.dir, entry), entry),
90159
90454
  updatedAt: latestMtime(entry.dir)
90160
90455
  }));
90161
- sendJson(res, 200, { project: import_node_path19.default.basename(import_node_path19.default.dirname(import_node_path19.default.dirname(root))), artifacts: summaries, warnings });
90456
+ sendJson(res, 200, { project: import_node_path20.default.basename(import_node_path20.default.dirname(import_node_path20.default.dirname(root))), artifacts: summaries, warnings });
90162
90457
  };
90163
90458
  function onClientText(socket, text2) {
90164
90459
  let message;
@@ -90220,7 +90515,7 @@ async function startArtifactsServer(options) {
90220
90515
  return;
90221
90516
  }
90222
90517
  if (pathname === "/robots.txt") {
90223
- sendFile(res, import_node_path19.default.join(viewerDir, "robots.txt"));
90518
+ sendFile(res, import_node_path20.default.join(viewerDir, "robots.txt"));
90224
90519
  return;
90225
90520
  }
90226
90521
  if (pathname.startsWith("/c/")) {
@@ -90302,6 +90597,24 @@ async function startArtifactsServer(options) {
90302
90597
  });
90303
90598
  return;
90304
90599
  }
90600
+ const agentBoardMatch = API_AGENT_BOARD_COMMENTS_ROUTE.exec(pathname);
90601
+ if (agentBoardMatch) {
90602
+ if (req.method !== "POST" || key2 === null || !safeEqual(key2, token)) {
90603
+ send(res, 403, "Forbidden: agent calls need ?key=");
90604
+ return;
90605
+ }
90606
+ void serveAgentBoardComments(req, res, agentBoardMatch[1]).catch((err) => {
90607
+ if (!res.headersSent) send(res, 500, `Artifacts error: ${err.message}`);
90608
+ });
90609
+ return;
90610
+ }
90611
+ const boardCommentsMatch = API_BOARD_COMMENTS_ROUTE.exec(pathname);
90612
+ if (boardCommentsMatch) {
90613
+ void serveBoardComments(req, res, boardCommentsMatch[1]).catch((err) => {
90614
+ if (!res.headersSent) send(res, 500, `Artifacts error: ${err.message}`);
90615
+ });
90616
+ return;
90617
+ }
90305
90618
  const documentMatch = API_DOCUMENT_ROUTE.exec(pathname);
90306
90619
  if (documentMatch) {
90307
90620
  void serveDocumentApi(req, res, documentMatch[1], documentMatch[2]).catch((err) => {
@@ -90317,7 +90630,7 @@ async function startArtifactsServer(options) {
90317
90630
  if (SHELL_ROUTE.test(pathname) || PREVIEW_ROUTE.test(pathname) || pathname === "/index.html") {
90318
90631
  const ref = artifactKeyOf(pathname.replace(/^\/(?:preview\/)?/, "").replace(/\/$/, ""));
90319
90632
  const dir = ref && resolveArtifactDir(root, ref.type, ref.id);
90320
- const project = import_node_path19.default.basename(import_node_path19.default.dirname(import_node_path19.default.dirname(root)));
90633
+ const project = import_node_path20.default.basename(import_node_path20.default.dirname(import_node_path20.default.dirname(root)));
90321
90634
  const title = ref ? dir ? titleOf(readArtifact(dir, ref), ref) : ref.id : `${project} \xB7 Artifacts`;
90322
90635
  const description = ref ? `\u5728 ${project} \u9879\u76EE\u4E2D\u67E5\u770B ${title}\uFF0C\u8FD9\u662F\u4E00\u4E2A ${ref.type === "artboard" ? "Artboard" : ref.type === "document" ? "Document" : "HTML"} Artifact\u3002` : `\u6D4F\u89C8 ${project} \u9879\u76EE\u7684\u5168\u90E8 Artboard\u3001Document \u548C HTML Artifact\uFF0C\u9884\u89C8\u771F\u5B9E\u5185\u5BB9\uFF0C\u6309\u7C7B\u578B\u3001\u540D\u79F0\u548C\u66F4\u65B0\u65F6\u95F4\u67E5\u627E\u4F5C\u54C1\u3002`;
90323
90636
  const canonical = `http://${urlHostForHttp(options.urlHost)}:${port}/${ref ? `${ref.type}/${ref.id}` : ""}`;
@@ -90326,7 +90639,7 @@ async function startArtifactsServer(options) {
90326
90639
  "Content-Security-Policy": PREVIEW_ROUTE.test(pathname) ? VIEWER_CSP.replace("frame-ancestors 'none'", "frame-ancestors 'self'") : VIEWER_CSP
90327
90640
  };
90328
90641
  if (key2 !== null) headers["Set-Cookie"] = `${cookieName}=${token}; HttpOnly; SameSite=Strict; Path=/`;
90329
- void import_node_fs22.default.promises.readFile(import_node_path19.default.join(viewerDir, "index.html"), "utf8").then((template) => send(res, 200, viewerPage(template, { title, description, url: canonical }), headers)).catch((err) => send(res, err.code === "ENOENT" ? 404 : 500, "Viewer unavailable"));
90642
+ void import_node_fs23.default.promises.readFile(import_node_path20.default.join(viewerDir, "index.html"), "utf8").then((template) => send(res, 200, viewerPage(template, { title, description, url: canonical }), headers)).catch((err) => send(res, err.code === "ENOENT" ? 404 : 500, "Viewer unavailable"));
90330
90643
  return;
90331
90644
  }
90332
90645
  const target = resolveInside(viewerDir, pathname.slice(1));
@@ -90385,7 +90698,7 @@ async function startArtifactsServer(options) {
90385
90698
  port = await listen(server, options.host, 0);
90386
90699
  }
90387
90700
  cookieName = `ryugi_artifacts_${port}`;
90388
- import_node_fs22.default.writeFileSync(portFile, `${port}
90701
+ import_node_fs23.default.writeFileSync(portFile, `${port}
90389
90702
  `, { mode: 384 });
90390
90703
  const origin = `http://${urlHostForHttp(options.urlHost)}:${port}`;
90391
90704
  const url = `${origin}/?key=${token}`;
@@ -90402,7 +90715,7 @@ async function startArtifactsServer(options) {
90402
90715
  return payload;
90403
90716
  };
90404
90717
  const refreshState = (ref, dir) => {
90405
- signatures.set(`${ref.type}/${ref.id}`, artifactSignature(dir));
90718
+ signatures.set(`${ref.type}/${ref.id}`, artifactSignature(dir, ref.type));
90406
90719
  return writeState(ref, dir);
90407
90720
  };
90408
90721
  const initialChanges = [];
@@ -90411,7 +90724,7 @@ async function startArtifactsServer(options) {
90411
90724
  initialChanges.push({ ref: entry, payload, files: [] });
90412
90725
  if (entry.type === "document") void documents.open(entry.id);
90413
90726
  }
90414
- writeJsonAtomic(import_node_path19.default.join(stateDir, "server-info"), { type: "server-started", url, port, root });
90727
+ writeJsonAtomic(import_node_path20.default.join(stateDir, "server-info"), { type: "server-started", url, port, root });
90415
90728
  const relayViewers = /* @__PURE__ */ new Map();
90416
90729
  const servedArtifacts = () => {
90417
90730
  const served = /* @__PURE__ */ new Map();
@@ -90444,6 +90757,11 @@ async function startArtifactsServer(options) {
90444
90757
  if (typeof request.message === "object" && request.message !== null) acceptReport({ ...request.message, artifact: key2 });
90445
90758
  return null;
90446
90759
  }
90760
+ if (request.op === "comment" && ref.type === "artboard") {
90761
+ const op = JSON.stringify(request.comment ?? null).length <= MAX_COMMENT_OP ? asBoardHumanOp(request.comment) : null;
90762
+ if (!op) throw new Error("not a comment op");
90763
+ return boardComments.apply(ref.id, op);
90764
+ }
90447
90765
  if ((request.op === "state" || request.op === "update" || request.op === "comment") && ref.type !== "document") throw new Error(`${key2} is not a document`);
90448
90766
  if (request.op === "state" || request.op === "update") {
90449
90767
  const session = await documents.open(ref.id);
@@ -90508,7 +90826,9 @@ async function startArtifactsServer(options) {
90508
90826
  }
90509
90827
  if (ref.type === "document" && (changed.includes("doc.xml") || changed.length === 0)) documents.fileChanged(ref.id);
90510
90828
  if (ref.type === "document" && changed.includes("comments.json")) documents.commentsChanged(ref.id);
90511
- if (signatures.get(key2) === artifactSignature(dir)) continue;
90829
+ if (ref.type === "artboard" && changed.includes("comments.json")) boardComments.sync(ref.id);
90830
+ if (ref.type === "artboard" && (changed.some((file) => file !== "comments.json") || changed.length === 0)) boardComments.boardsChanged(ref.id);
90831
+ if (signatures.get(key2) === artifactSignature(dir, ref.type)) continue;
90512
90832
  autoPublisher?.changed(key2);
90513
90833
  runtimeWarnings.delete(key2);
90514
90834
  const payload = refreshState(ref, dir);
@@ -90552,7 +90872,7 @@ async function startArtifactsServer(options) {
90552
90872
  }
90553
90873
 
90554
90874
  // src/server/lifecycle.ts
90555
- var import_node_fs23 = __toESM(require("node:fs"), 1);
90875
+ var import_node_fs24 = __toESM(require("node:fs"), 1);
90556
90876
  var DEFAULT_IDLE_TIMEOUT_MS = 4 * 60 * 60 * 1e3;
90557
90877
  var DEFAULT_LIFECYCLE_CHECK_MS = 60 * 1e3;
90558
90878
  var SERVER_ID_ARG = "--artifacts-server-id=";
@@ -90584,7 +90904,7 @@ function parseEnv(env, argv = []) {
90584
90904
  function readOwners(file) {
90585
90905
  let text2;
90586
90906
  try {
90587
- text2 = import_node_fs23.default.readFileSync(file, "utf8");
90907
+ text2 = import_node_fs24.default.readFileSync(file, "utf8");
90588
90908
  } catch {
90589
90909
  return [];
90590
90910
  }
@@ -90647,20 +90967,20 @@ function openReady(payload) {
90647
90967
  }
90648
90968
 
90649
90969
  // package.json
90650
- var version = "1.15.1";
90970
+ var version = "1.16.0";
90651
90971
 
90652
90972
  // src/server/main.ts
90653
90973
  async function main() {
90654
90974
  const config = parseEnv(process.env, process.argv.slice(2));
90655
- const root = import_node_path20.default.resolve(config.root);
90656
- const stateDir = import_node_path20.default.join(root, ".state");
90975
+ const root = import_node_path21.default.resolve(config.root);
90976
+ const stateDir = import_node_path21.default.join(root, ".state");
90657
90977
  const server = await startArtifactsServer({
90658
90978
  root,
90659
- viewerDir: import_node_path20.default.join(__dirname, "viewer"),
90660
- runtimeDir: import_node_path20.default.join(__dirname, "runtime"),
90979
+ viewerDir: import_node_path21.default.join(__dirname, "viewer"),
90980
+ runtimeDir: import_node_path21.default.join(__dirname, "runtime"),
90661
90981
  host: config.host,
90662
90982
  urlHost: config.urlHost,
90663
- configFile: import_node_path20.default.join(import_node_path20.default.dirname(root), "config.json"),
90983
+ configFile: import_node_path21.default.join(import_node_path21.default.dirname(root), "config.json"),
90664
90984
  cloud: { origin: config.cloudOrigin, credentialsFile: credentialsFile(process.env, process.platform, import_node_os.default.homedir()) },
90665
90985
  relay: { url: `${config.cloudOrigin.replace(/^http/, "ws")}/api/relay/host`, version }
90666
90986
  });
@@ -90690,12 +91010,12 @@ async function main() {
90690
91010
  if (stopping) return;
90691
91011
  stopping = true;
90692
91012
  console.log(JSON.stringify({ type: "server-stopped", reason }));
90693
- import_node_fs24.default.rmSync(import_node_path20.default.join(stateDir, "server-info"), { force: true });
90694
- writeJsonAtomic(import_node_path20.default.join(stateDir, "server-stopped"), { reason, timestamp: Date.now() });
91013
+ import_node_fs25.default.rmSync(import_node_path21.default.join(stateDir, "server-info"), { force: true });
91014
+ writeJsonAtomic(import_node_path21.default.join(stateDir, "server-stopped"), { reason, timestamp: Date.now() });
90695
91015
  await server.close();
90696
91016
  process.exit(0);
90697
91017
  };
90698
- const ownersFile = import_node_path20.default.join(stateDir, "owners");
91018
+ const ownersFile = import_node_path21.default.join(stateDir, "owners");
90699
91019
  const timer = setInterval(() => {
90700
91020
  const owners = readOwners(ownersFile);
90701
91021
  const reason = shutdownReason({
@@ -90709,7 +91029,7 @@ async function main() {
90709
91029
  timer.unref();
90710
91030
  process.on("SIGTERM", () => void shutdown("SIGTERM"));
90711
91031
  process.on("SIGINT", () => void shutdown("SIGINT"));
90712
- import_node_fs24.default.rmSync(import_node_path20.default.join(stateDir, "server-stopped"), { force: true });
91032
+ import_node_fs25.default.rmSync(import_node_path21.default.join(stateDir, "server-stopped"), { force: true });
90713
91033
  console.log(JSON.stringify({ type: "server-started", url: server.url, port: server.port, root }));
90714
91034
  }
90715
91035
  main().catch((err) => {