ryugi-artifacts 1.15.1 → 1.15.2

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 (147) hide show
  1. package/dist/cli.cjs +48 -15
  2. package/dist/server.cjs +560 -249
  3. package/dist/viewer/CloudDocument-a-QKYAcP.js +1 -0
  4. package/dist/viewer/CloudLiveDocument-9iIvfZ3y.js +1 -0
  5. package/dist/viewer/{DocumentLayout-BDQ2bRgn.js → DocumentLayout-C7DI3nfX.js} +82 -82
  6. package/dist/viewer/DocumentPage-CN-MKF7P.js +1 -0
  7. package/dist/viewer/{abnfDiagram-VCTEODGH-DXcz0DY8.js → abnfDiagram-VCTEODGH-BMWz464k.js} +1 -1
  8. package/dist/viewer/{ar-SA-G6X2FPQ2-DaFICdeh.js → ar-SA-G6X2FPQ2-BbVC-Hlr.js} +1 -1
  9. package/dist/viewer/{architectureDiagram-5GKGNRK7-7HwBC9Wz.js → architectureDiagram-5GKGNRK7-lX00q8y2.js} +1 -1
  10. package/dist/viewer/{az-AZ-76LH7QW2-ro71YLew.js → az-AZ-76LH7QW2-kWbxvJoq.js} +1 -1
  11. package/dist/viewer/{bg-BG-XCXSNQG7-BBql1i2c.js → bg-BG-XCXSNQG7-TWjfBbIo.js} +1 -1
  12. package/dist/viewer/{blockDiagram-I7D4REHJ-DN6_CCIZ.js → blockDiagram-I7D4REHJ-r6mTM-V-.js} +1 -1
  13. package/dist/viewer/{bn-BD-2XOGV67Q-D0nEk_sk.js → bn-BD-2XOGV67Q-Dt8TooV0.js} +1 -1
  14. package/dist/viewer/{c4Diagram-7LVT6UL2-BiKuFZ_0.js → c4Diagram-7LVT6UL2-C-_q-X-w.js} +1 -1
  15. package/dist/viewer/{ca-ES-6MX7JW3Y-BzXg8v5p.js → ca-ES-6MX7JW3Y-D_8AE5qp.js} +1 -1
  16. package/dist/viewer/{chunk-4HAMMTFA-D8wduydj.js → chunk-4HAMMTFA-VvmFCZU-.js} +1 -1
  17. package/dist/viewer/{chunk-EIO257PC-KVQLe9-1.js → chunk-EIO257PC-BD0aoNze.js} +1 -1
  18. package/dist/viewer/{chunk-GMAD6QVW-BPxTHMTi.js → chunk-GMAD6QVW-DWAZVXaZ.js} +1 -1
  19. package/dist/viewer/{chunk-GVQU2GXP-C5_2EAAn.js → chunk-GVQU2GXP-BXHdLj9j.js} +1 -1
  20. package/dist/viewer/{chunk-IMKFNOWR-CoST25Zy.js → chunk-IMKFNOWR-Fyso0OP0.js} +1 -1
  21. package/dist/viewer/{chunk-L3NEJ4N5-DqtaXAcI.js → chunk-L3NEJ4N5-B4STdBka.js} +1 -1
  22. package/dist/viewer/{chunk-OSK3NFVY-DacvL7tA.js → chunk-OSK3NFVY-BEwpVZFI.js} +1 -1
  23. package/dist/viewer/{chunk-PWAF6VOD-Cxj_hOdt.js → chunk-PWAF6VOD-DdxzGU7i.js} +1 -1
  24. package/dist/viewer/{chunk-SHT3W25Y-D9pKJ6Au.js → chunk-SHT3W25Y-vfyQBaPf.js} +1 -1
  25. package/dist/viewer/{chunk-SVP7TREG-svneeNVA.js → chunk-SVP7TREG-iglWUDOS.js} +1 -1
  26. package/dist/viewer/{chunk-TICWLB2K-xfr9ROyW.js → chunk-TICWLB2K-cw2jyncd.js} +1 -1
  27. package/dist/viewer/{classDiagram-ZZMXUADV-lu71gUXJ.js → classDiagram-ZZMXUADV-2wFXqaaX.js} +1 -1
  28. package/dist/viewer/{classDiagram-v2-VYDZK3BY-lu71gUXJ.js → classDiagram-v2-VYDZK3BY-2wFXqaaX.js} +1 -1
  29. package/dist/viewer/{cs-CZ-2BRQDIVT-bwZx9UIu.js → cs-CZ-2BRQDIVT-BYqh8Zl-.js} +1 -1
  30. package/dist/viewer/{cynefinDiagram-5FMLGOSQ-DlNaJbj_.js → cynefinDiagram-5FMLGOSQ-CHJoO2Gh.js} +1 -1
  31. package/dist/viewer/{da-DK-5WZEPLOC-BApDVcwE.js → da-DK-5WZEPLOC-DeKrLDAU.js} +1 -1
  32. package/dist/viewer/{dagre-GXQ25YYZ-DWu2Ycc5.js → dagre-GXQ25YYZ-GWluYfqf.js} +1 -1
  33. package/dist/viewer/{de-DE-XR44H4JA-COcHc6Zt.js → de-DE-XR44H4JA-D1wSMLDv.js} +1 -1
  34. package/dist/viewer/{diagram-S7CK7UJ4-D8VO7YKc.js → diagram-S7CK7UJ4-BlgVPVRQ.js} +1 -1
  35. package/dist/viewer/{diagram-UQ7AKVKN-D5OcQNY9.js → diagram-UQ7AKVKN-ChtP0cpA.js} +1 -1
  36. package/dist/viewer/{diagram-VX7I27RA-DRHmiAWI.js → diagram-VX7I27RA-vKg6VXhQ.js} +1 -1
  37. package/dist/viewer/{diagram-Z3DM3KII-ClXzWLem.js → diagram-Z3DM3KII-CuHg0fFk.js} +1 -1
  38. package/dist/viewer/{dist-BuKe8NdN.js → dist-B0CwGaG1.js} +1 -1
  39. package/dist/viewer/{dist-BKtxxS1a.js → dist-BPFUf7bl.js} +1 -1
  40. package/dist/viewer/{dist-_3NL_2LS.js → dist-Bh74XfyN.js} +1 -1
  41. package/dist/viewer/{dist-TX5rYYGW.js → dist-BsTr5QXe.js} +4 -4
  42. package/dist/viewer/{dist-CFkCXjQP.js → dist-C0tpKGuj.js} +1 -1
  43. package/dist/viewer/{dist-DB3o8dUU.js → dist-CKWiXiZi.js} +1 -1
  44. package/dist/viewer/{dist-CV7QdHTq.js → dist-CSqVlzxR.js} +1 -1
  45. package/dist/viewer/{dist-DlANbAUX.js → dist-Cn_FmF1G.js} +1 -1
  46. package/dist/viewer/{dist-DzBPEHnz.js → dist-CtEVZbP8.js} +1 -1
  47. package/dist/viewer/{dist-C2adJeNZ.js → dist-D2qf-mJJ.js} +1 -1
  48. package/dist/viewer/{dist-DLh5uREE.js → dist-DM8QiMoo.js} +1 -1
  49. package/dist/viewer/{dist-DcGUli8O.js → dist-DRSecFjm.js} +1 -1
  50. package/dist/viewer/{dist-G2JHMLgK.js → dist-DX-BoDBd.js} +1 -1
  51. package/dist/viewer/{dist-VupXLYv6.js → dist-DY66SSSM.js} +1 -1
  52. package/dist/viewer/{dist-M7Lz5R2v.js → dist-D_BoFupz.js} +1 -1
  53. package/dist/viewer/{dist-C_NwF-ep.js → dist-DeIwlPUC.js} +1 -1
  54. package/dist/viewer/{dist-Bgijnk4D.js → dist-Dnus25TG.js} +1 -1
  55. package/dist/viewer/{dist-D0Emc84u.js → dist-DqIYAdWh.js} +1 -1
  56. package/dist/viewer/{dist-rgdCugHI.js → dist-DygF9RCe.js} +1 -1
  57. package/dist/viewer/{dist-a8BuHhYq.js → dist-aWI_fVHd.js} +1 -1
  58. package/dist/viewer/{dist-mDWB-P3S.js → dist-k50hDI1z.js} +1 -1
  59. package/dist/viewer/{dist-DfWxOyg4.js → dist-rnL-4FWR.js} +1 -1
  60. package/dist/viewer/{dist-q1jFGEbP.js → dist-uFBCfcc5.js} +1 -1
  61. package/dist/viewer/{dist-CByD4sdq.js → dist-zBXj6chJ.js} +1 -1
  62. package/dist/viewer/{ebnfDiagram-PWID7BFC-BOzsyHEk.js → ebnfDiagram-PWID7BFC-C5bMbUq0.js} +1 -1
  63. package/dist/viewer/{el-GR-BZB4AONW-h1eLQlP7.js → el-GR-BZB4AONW-0cbbZg2F.js} +1 -1
  64. package/dist/viewer/{en-B4ZKOASM-EdleGu9D.js → en-B4ZKOASM-CDTpThHV.js} +1 -1
  65. package/dist/viewer/{erDiagram-RLTQ6QDP-BF_tpcM3.js → erDiagram-RLTQ6QDP-C6PFBM-5.js} +1 -1
  66. package/dist/viewer/{es-ES-U4NZUMDT-DeaeHEK9.js → es-ES-U4NZUMDT-8fQP93wk.js} +1 -1
  67. package/dist/viewer/{eu-ES-A7QVB2H4-BzoRNYbw.js → eu-ES-A7QVB2H4-CnogIziV.js} +1 -1
  68. package/dist/viewer/{fa-IR-HGAKTJCU-fXYoNuWo.js → fa-IR-HGAKTJCU-CdYOaxCM.js} +1 -1
  69. package/dist/viewer/{fi-FI-Z5N7JZ37-CTVEZXqM.js → fi-FI-Z5N7JZ37-Ds2DlP3M.js} +1 -1
  70. package/dist/viewer/flowDiagram-HODETNUW-DFPpbdLC.js +1 -0
  71. package/dist/viewer/{fr-FR-RHASNOE6-BizkpgaG.js → fr-FR-RHASNOE6-BZT79zkJ.js} +1 -1
  72. package/dist/viewer/{ganttDiagram-EL5Y4UJY-DvL8TKGV.js → ganttDiagram-EL5Y4UJY-Dj5h1KL5.js} +1 -1
  73. package/dist/viewer/{gl-ES-HMX3MZ6V-rsfHb2R9.js → gl-ES-HMX3MZ6V-D5SkTUmd.js} +1 -1
  74. package/dist/viewer/{he-IL-6SHJWFNN-BmWEwAWC.js → he-IL-6SHJWFNN-DAYxQbJv.js} +1 -1
  75. package/dist/viewer/{hi-IN-IWLTKZ5I-gzYZTFIt.js → hi-IN-IWLTKZ5I-CmTpu_Mh.js} +1 -1
  76. package/dist/viewer/{hu-HU-A5ZG7DT2-DqXGHJ4g.js → hu-HU-A5ZG7DT2-CCoJDqVh.js} +1 -1
  77. package/dist/viewer/{id-ID-SAP4L64H-CVbSVAc3.js → id-ID-SAP4L64H-DvB8T5Ih.js} +1 -1
  78. package/dist/viewer/{image-GAAHSSAO-B0g7YyLf.js → image-GAAHSSAO-lz2X8afW.js} +1 -1
  79. package/dist/viewer/index.html +1 -1
  80. package/dist/viewer/index.js +8 -8
  81. package/dist/viewer/index2.css +66 -18
  82. package/dist/viewer/{infoDiagram-27XIBGKW-DOxIYIRL.js → infoDiagram-27XIBGKW-CPqqoefO.js} +1 -1
  83. package/dist/viewer/{ishikawaDiagram-5VMMS53U-D-U6Fi8F.js → ishikawaDiagram-5VMMS53U-CWt0edvi.js} +1 -1
  84. package/dist/viewer/{it-IT-JPQ66NNP-6PNiI36L.js → it-IT-JPQ66NNP-X0-NsLIF.js} +1 -1
  85. package/dist/viewer/{ja-JP-DBVTYXUO-D3SJtNp5.js → ja-JP-DBVTYXUO-DXggnPKV.js} +1 -1
  86. package/dist/viewer/{journeyDiagram-3NMN7TZE-8Mz0Nae3.js → journeyDiagram-3NMN7TZE-CiTGpewC.js} +1 -1
  87. package/dist/viewer/{kaa-6HZHGXH3-Bz3WA5yL.js → kaa-6HZHGXH3-2DqCJWwE.js} +1 -1
  88. package/dist/viewer/{kab-KAB-ZGHBKWFO-W2PtzMEV.js → kab-KAB-ZGHBKWFO-BMae-gRZ.js} +1 -1
  89. package/dist/viewer/{kanban-definition-UXKFOSKX-BP8HwPzL.js → kanban-definition-UXKFOSKX-BdzkoXVg.js} +1 -1
  90. package/dist/viewer/{kk-KZ-P5N5QNE5-Dc1i0Bup.js → kk-KZ-P5N5QNE5-BrUDCVOJ.js} +1 -1
  91. package/dist/viewer/{km-KH-HSX4SM5Z-C88_w0rR.js → km-KH-HSX4SM5Z-D8gJ0fJb.js} +1 -1
  92. package/dist/viewer/{ko-KR-MTYHY66A-DWOt-6As.js → ko-KR-MTYHY66A-BiY1FmA2.js} +1 -1
  93. package/dist/viewer/{ku-TR-6OUDTVRD-B7qeFzlo.js → ku-TR-6OUDTVRD-CG1Rotv4.js} +1 -1
  94. package/dist/viewer/{lt-LT-XHIRWOB4-Dc9eguDu.js → lt-LT-XHIRWOB4-BmDMD56e.js} +1 -1
  95. package/dist/viewer/{lv-LV-5QDEKY6T-D9CzWKMj.js → lv-LV-5QDEKY6T-DDGbepyo.js} +1 -1
  96. package/dist/viewer/{mindmap-definition-YA3MSWOX-Bikv3SLv.js → mindmap-definition-YA3MSWOX-CQZcwz8L.js} +1 -1
  97. package/dist/viewer/{mr-IN-CRQNXWMA-CtlsKIl9.js → mr-IN-CRQNXWMA-DGVOXAif.js} +1 -1
  98. package/dist/viewer/{my-MM-5M5IBNSE-CiyAGqHH.js → my-MM-5M5IBNSE-CE47hNzn.js} +1 -1
  99. package/dist/viewer/{nb-NO-T6EIAALU-l9uMRUdk.js → nb-NO-T6EIAALU-DWAPtr4M.js} +1 -1
  100. package/dist/viewer/{nl-NL-IS3SIHDZ-zja_zaqN.js → nl-NL-IS3SIHDZ-CSqbImvS.js} +1 -1
  101. package/dist/viewer/{nn-NO-6E72VCQL-BiP5RLsf.js → nn-NO-6E72VCQL-CjGcKERL.js} +1 -1
  102. package/dist/viewer/{oc-FR-POXYY2M6-CvKUzwab.js → oc-FR-POXYY2M6-xGWsukeR.js} +1 -1
  103. package/dist/viewer/{pa-IN-N4M65BXN-DioFchQd.js → pa-IN-N4M65BXN-BJE8h_ZN.js} +1 -1
  104. package/dist/viewer/{pegDiagram-XKGWAZYB-BSllNtPI.js → pegDiagram-XKGWAZYB-CcuWZbUH.js} +1 -1
  105. package/dist/viewer/percentages-BXMCSKIN-PFePCwe-.js +1 -0
  106. package/dist/viewer/{pieDiagram-E7YTZNPT-b1-BsaA4.js → pieDiagram-E7YTZNPT-DmDiBmYg.js} +1 -1
  107. package/dist/viewer/{pl-PL-T2D74RX3-Ci7dAnI8.js → pl-PL-T2D74RX3-Uk8kN-ZN.js} +1 -1
  108. package/dist/viewer/{prod-CPZkgD7t.js → prod-BUf-ByWA.js} +8 -8
  109. package/dist/viewer/{pt-BR-5N22H2LF--VT7NYYE.js → pt-BR-5N22H2LF-gBczIC_J.js} +1 -1
  110. package/dist/viewer/{pt-PT-UZXXM6DQ-CWdbQilC.js → pt-PT-UZXXM6DQ-BGwa_O4j.js} +1 -1
  111. package/dist/viewer/{quadrantDiagram-AXDQQJYC-D0pucWR0.js → quadrantDiagram-AXDQQJYC-T0I-ZZTk.js} +1 -1
  112. package/dist/viewer/{railroadDiagram-O6MQD6OU-DIPJ2PKr.js → railroadDiagram-O6MQD6OU-BU8Pl_j-.js} +1 -1
  113. package/dist/viewer/{requirementDiagram-BXWQKSXE-DEGhiwJ9.js → requirementDiagram-BXWQKSXE-BJ8oJEnw.js} +1 -1
  114. package/dist/viewer/{ro-RO-JPDTUUEW-BK4wLUL7.js → ro-RO-JPDTUUEW-pdcrUiy_.js} +1 -1
  115. package/dist/viewer/{ru-RU-B4JR7IUQ-BDPQ8blU.js → ru-RU-B4JR7IUQ-CVgnNF41.js} +1 -1
  116. package/dist/viewer/{sequenceDiagram-WJ2MYXX4-D7sLhGYb.js → sequenceDiagram-WJ2MYXX4-gJZVdUNR.js} +1 -1
  117. package/dist/viewer/{si-LK-N5RQ5JYF-D5DWsUAA.js → si-LK-N5RQ5JYF-CSt8gCsq.js} +1 -1
  118. package/dist/viewer/{sk-SK-C5VTKIMK-Cia9j1Te.js → sk-SK-C5VTKIMK-XfhHoKBc.js} +1 -1
  119. package/dist/viewer/{sl-SI-NN7IZMDC-CV304k5j.js → sl-SI-NN7IZMDC-CCi2FT_f.js} +1 -1
  120. package/dist/viewer/{stateDiagram-D77RDMKH-BMNx9_M8.js → stateDiagram-D77RDMKH-BdnhW-mL.js} +1 -1
  121. package/dist/viewer/{stateDiagram-v2-MP3YSRHH-CPa9DZs4.js → stateDiagram-v2-MP3YSRHH-DKQ7Wxmz.js} +1 -1
  122. package/dist/viewer/subset-shared.chunk-VczM4DdU.js +1 -0
  123. package/dist/viewer/{subset-worker.chunk-CHQzlaj3.js → subset-worker.chunk-lTXEvH8k.js} +1 -1
  124. package/dist/viewer/{sv-SE-XGPEYMSR-CXdp-XZD.js → sv-SE-XGPEYMSR-Cyx0oXFg.js} +1 -1
  125. package/dist/viewer/{swimlanes-42K2YHIH-BwUyxwi3.js → swimlanes-42K2YHIH-BEaqSHwN.js} +1 -1
  126. package/dist/viewer/{swimlanesDiagram-VR7AAH4N-BgxS36EG.js → swimlanesDiagram-VR7AAH4N-Bed4kux2.js} +1 -1
  127. package/dist/viewer/{ta-IN-2NMHFXQM-CObY6q-8.js → ta-IN-2NMHFXQM-B7PlQhT2.js} +1 -1
  128. package/dist/viewer/{th-TH-HPSO5L25-CEOiELZN.js → th-TH-HPSO5L25-CjkmlCNK.js} +1 -1
  129. package/dist/viewer/{timeline-definition-24CTP7MA-BJ-7Fdfr.js → timeline-definition-24CTP7MA-Bej-g5qJ.js} +1 -1
  130. package/dist/viewer/{tr-TR-DEFEU3FU-CPj5dFcB.js → tr-TR-DEFEU3FU-Cjo4Cn6F.js} +1 -1
  131. package/dist/viewer/{uk-UA-QMV73CPH-BkZyFeXP.js → uk-UA-QMV73CPH-XHZw5PVj.js} +1 -1
  132. package/dist/viewer/useSandboxFrame-CCmB0PzU.js +58 -0
  133. package/dist/viewer/{vennDiagram-4TSXK5OY-DnFbjWDw.js → vennDiagram-4TSXK5OY-CDh6q_u6.js} +1 -1
  134. package/dist/viewer/{vi-VN-M7AON7JQ-CkKKOpBQ.js → vi-VN-M7AON7JQ-9gMVxytt.js} +1 -1
  135. package/dist/viewer/{wardleyDiagram-VM6X3IG4-CN_pm__V.js → wardleyDiagram-VM6X3IG4-DKIOQyM_.js} +1 -1
  136. package/dist/viewer/{xychartDiagram-S5SC5T6Z-C7XLywXm.js → xychartDiagram-S5SC5T6Z-Cb3e4r6i.js} +1 -1
  137. package/dist/viewer/{zh-CN-LNUGB5OW-BQcCqsDg.js → zh-CN-LNUGB5OW-6d07k_8d.js} +1 -1
  138. package/dist/viewer/{zh-HK-E62DVLB3-Bp-jK0zw.js → zh-HK-E62DVLB3-BkpGiC7m.js} +1 -1
  139. package/dist/viewer/{zh-TW-RAJ6MFWO-KmFL88Bs.js → zh-TW-RAJ6MFWO-Br8z9s4c.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/flowDiagram-HODETNUW-DpY3t4b0.js +0 -1
  145. package/dist/viewer/percentages-BXMCSKIN-BRZnWvym.js +0 -1
  146. package/dist/viewer/subset-shared.chunk-ChUk9bHw.js +0 -1
  147. package/dist/viewer/useSandboxFrame-BpJ9uM86.js +0 -58
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
  });
@@ -88275,9 +88508,9 @@ function injectRuntime(html, options = {}) {
88275
88508
  }
88276
88509
 
88277
88510
  // 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);
88511
+ var import_node_crypto3 = __toESM(require("node:crypto"), 1);
88512
+ var import_node_fs10 = __toESM(require("node:fs"), 1);
88513
+ var import_node_path10 = __toESM(require("node:path"), 1);
88281
88514
  var MIME2 = {
88282
88515
  ".html": "text/html; charset=utf-8",
88283
88516
  ".js": "text/javascript; charset=utf-8",
@@ -88300,12 +88533,12 @@ var MIME2 = {
88300
88533
  ".mp4": "video/mp4",
88301
88534
  ".webm": "video/webm"
88302
88535
  };
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");
88536
+ var mimeOf = (file) => MIME2[import_node_path10.default.extname(file).toLowerCase()] ?? "application/octet-stream";
88537
+ var randomToken = () => import_node_crypto3.default.randomBytes(24).toString("base64url");
88305
88538
  function safeEqual(a5, b6) {
88306
88539
  const ab = Buffer.from(a5);
88307
88540
  const bb = Buffer.from(b6);
88308
- return ab.length === bb.length && import_node_crypto2.default.timingSafeEqual(ab, bb);
88541
+ return ab.length === bb.length && import_node_crypto3.default.timingSafeEqual(ab, bb);
88309
88542
  }
88310
88543
  function parseCookies(header) {
88311
88544
  const cookies = {};
@@ -88317,7 +88550,7 @@ function parseCookies(header) {
88317
88550
  }
88318
88551
  function readPersisted(file) {
88319
88552
  try {
88320
- return import_node_fs9.default.readFileSync(file, "utf8").trim();
88553
+ return import_node_fs10.default.readFileSync(file, "utf8").trim();
88321
88554
  } catch {
88322
88555
  return "";
88323
88556
  }
@@ -88346,15 +88579,15 @@ function sendJson(res, status, value) {
88346
88579
  send(res, status, JSON.stringify(value), { "Content-Type": MIME2[".json"] });
88347
88580
  }
88348
88581
  function sendFile(res, file, headers = {}) {
88349
- import_node_fs9.default.readFile(file, (err, data) => {
88582
+ import_node_fs10.default.readFile(file, (err, data) => {
88350
88583
  if (err) send(res, 404, "Not found");
88351
88584
  else send(res, 200, data, { "Content-Type": mimeOf(file), ...headers });
88352
88585
  });
88353
88586
  }
88354
88587
 
88355
88588
  // 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);
88589
+ var import_node_fs11 = __toESM(require("node:fs"), 1);
88590
+ var import_node_path11 = __toESM(require("node:path"), 1);
88358
88591
  function dynamicUrls(xml) {
88359
88592
  const urls = /* @__PURE__ */ new Set();
88360
88593
  for (const match of stripOpaque(xml).matchAll(TAG)) {
@@ -88367,7 +88600,7 @@ function dynamicUrls(xml) {
88367
88600
  }
88368
88601
  var mtimeOf = (file) => {
88369
88602
  try {
88370
- return import_node_fs10.default.statSync(file).mtimeMs;
88603
+ return import_node_fs11.default.statSync(file).mtimeMs;
88371
88604
  } catch {
88372
88605
  return -1;
88373
88606
  }
@@ -88384,7 +88617,7 @@ function createDynamicBlocks(limits) {
88384
88617
  };
88385
88618
  const entry = (id2, dir, file) => {
88386
88619
  const map = files(id2);
88387
- const mtime = mtimeOf(import_node_path10.default.join(dir, file));
88620
+ const mtime = mtimeOf(import_node_path11.default.join(dir, file));
88388
88621
  let state = map.get(file);
88389
88622
  if (!state || state.mtime !== mtime) {
88390
88623
  state = { mtime, errors: [], ran: false };
@@ -88409,19 +88642,19 @@ function createDynamicBlocks(limits) {
88409
88642
  state(id2, dir) {
88410
88643
  let xml = "";
88411
88644
  try {
88412
- xml = import_node_fs10.default.readFileSync(import_node_path10.default.join(dir, "doc.xml"), "utf8");
88645
+ xml = import_node_fs11.default.readFileSync(import_node_path11.default.join(dir, "doc.xml"), "utf8");
88413
88646
  } catch {
88414
88647
  return { warnings: [], dynamic: {} };
88415
88648
  }
88416
88649
  const urls = dynamicUrls(xml);
88417
88650
  const map = files(id2);
88418
88651
  for (const [file, state] of map) {
88419
- if (!urls.includes(file) || state.mtime !== mtimeOf(import_node_path10.default.join(dir, file))) map.delete(file);
88652
+ if (!urls.includes(file) || state.mtime !== mtimeOf(import_node_path11.default.join(dir, file))) map.delete(file);
88420
88653
  }
88421
88654
  const warnings = [];
88422
88655
  const dynamic = {};
88423
88656
  for (const url of urls) {
88424
- if (!import_node_fs10.default.existsSync(import_node_path10.default.join(dir, url))) warnings.push(`${url} not found`);
88657
+ if (!import_node_fs11.default.existsSync(import_node_path11.default.join(dir, url))) warnings.push(`${url} not found`);
88425
88658
  const state = map.get(url);
88426
88659
  for (const error of state?.errors ?? []) warnings.push(`runtime: ${url}: ${error}`);
88427
88660
  dynamic[url] = { ran: state?.ran ?? false };
@@ -88432,8 +88665,8 @@ function createDynamicBlocks(limits) {
88432
88665
  }
88433
88666
 
88434
88667
  // src/server/selection.ts
88435
- var import_node_fs11 = __toESM(require("node:fs"), 1);
88436
- var import_node_path11 = __toESM(require("node:path"), 1);
88668
+ var import_node_fs12 = __toESM(require("node:fs"), 1);
88669
+ var import_node_path12 = __toESM(require("node:path"), 1);
88437
88670
 
88438
88671
  // src/shared/selection.ts
88439
88672
  var SELECTION_FULL_NODES = 20;
@@ -88508,14 +88741,14 @@ function readAnySelection(value) {
88508
88741
  function createSelectionStore(stateDir, exists) {
88509
88742
  const tabs = /* @__PURE__ */ new Map();
88510
88743
  let clock = 0;
88511
- const fileOf = (kind, id2) => import_node_path11.default.join(stateDir, kind, id2, "selection.json");
88744
+ const fileOf = (kind, id2) => import_node_path12.default.join(stateDir, kind, id2, "selection.json");
88512
88745
  const write = (kind, id2) => {
88513
88746
  let winner;
88514
88747
  for (const tab of tabs.values()) {
88515
88748
  if (tab.kind !== kind || tab.id !== id2) continue;
88516
88749
  if (!winner || tab.humanAt > winner.humanAt || tab.humanAt === winner.humanAt && tab.seenAt > winner.seenAt) winner = tab;
88517
88750
  }
88518
- if (!winner?.state) import_node_fs11.default.rmSync(fileOf(kind, id2), { force: true });
88751
+ if (!winner?.state) import_node_fs12.default.rmSync(fileOf(kind, id2), { force: true });
88519
88752
  else writeJsonAtomic(fileOf(kind, id2), { artifact: `${kind}/${id2}`, updatedAt: (/* @__PURE__ */ new Date()).toISOString(), ...winner.state });
88520
88753
  };
88521
88754
  return {
@@ -88546,19 +88779,19 @@ function createSelectionStore(stateDir, exists) {
88546
88779
  for (const kind of ["artboard", "document"]) {
88547
88780
  let ids = [];
88548
88781
  try {
88549
- ids = import_node_fs11.default.readdirSync(import_node_path11.default.join(stateDir, kind));
88782
+ ids = import_node_fs12.default.readdirSync(import_node_path12.default.join(stateDir, kind));
88550
88783
  } catch {
88551
88784
  continue;
88552
88785
  }
88553
- for (const id2 of ids) import_node_fs11.default.rmSync(fileOf(kind, id2), { force: true });
88786
+ for (const id2 of ids) import_node_fs12.default.rmSync(fileOf(kind, id2), { force: true });
88554
88787
  }
88555
88788
  }
88556
88789
  };
88557
88790
  }
88558
88791
 
88559
88792
  // src/server/config.ts
88560
- var import_node_fs12 = __toESM(require("node:fs"), 1);
88561
- var import_node_path12 = __toESM(require("node:path"), 1);
88793
+ var import_node_fs13 = __toESM(require("node:fs"), 1);
88794
+ var import_node_path13 = __toESM(require("node:path"), 1);
88562
88795
 
88563
88796
  // src/shared/cloud.ts
88564
88797
  var ownLink = (url, hostId) => hostId ? `${url}#ryugi-host=${hostId}` : url;
@@ -88593,7 +88826,7 @@ function normalize(raw) {
88593
88826
  function readConfig(file) {
88594
88827
  let text2;
88595
88828
  try {
88596
- text2 = import_node_fs12.default.readFileSync(file, "utf8");
88829
+ text2 = import_node_fs13.default.readFileSync(file, "utf8");
88597
88830
  } catch {
88598
88831
  return { config: defaults() };
88599
88832
  }
@@ -88604,22 +88837,22 @@ function readConfig(file) {
88604
88837
  }
88605
88838
  }
88606
88839
  function ensureGitignore(dir) {
88607
- const file = import_node_path12.default.join(dir, ".gitignore");
88840
+ const file = import_node_path13.default.join(dir, ".gitignore");
88608
88841
  let current = "";
88609
88842
  try {
88610
- current = import_node_fs12.default.readFileSync(file, "utf8");
88843
+ current = import_node_fs13.default.readFileSync(file, "utf8");
88611
88844
  } catch {
88612
- import_node_fs12.default.writeFileSync(file, "config.json\n");
88845
+ import_node_fs13.default.writeFileSync(file, "config.json\n");
88613
88846
  return;
88614
88847
  }
88615
88848
  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
88849
+ import_node_fs13.default.writeFileSync(file, `${current}${current === "" || current.endsWith("\n") ? "" : "\n"}config.json
88617
88850
  `);
88618
88851
  }
88619
88852
  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());
88853
+ import_node_fs13.default.mkdirSync(import_node_path13.default.dirname(file), { recursive: true });
88854
+ ensureGitignore(import_node_path13.default.dirname(file));
88855
+ if (!import_node_fs13.default.existsSync(file)) writeJsonAtomic(file, defaults());
88623
88856
  }
88624
88857
  function updateArtifactConfig(file, key2, patch) {
88625
88858
  const { config, warning } = readConfig(file);
@@ -88639,21 +88872,21 @@ function setProjectAutoPublish(file, value) {
88639
88872
  if (warning) throw new Error(warning);
88640
88873
  if (value === null) delete config.autoPublish;
88641
88874
  else config.autoPublish = value;
88642
- import_node_fs12.default.mkdirSync(import_node_path12.default.dirname(file), { recursive: true });
88875
+ import_node_fs13.default.mkdirSync(import_node_path13.default.dirname(file), { recursive: true });
88643
88876
  writeJsonAtomic(file, config);
88644
88877
  }
88645
88878
 
88646
88879
  // src/server/credentials.ts
88647
- var import_node_fs13 = __toESM(require("node:fs"), 1);
88648
- var import_node_path13 = __toESM(require("node:path"), 1);
88880
+ var import_node_fs14 = __toESM(require("node:fs"), 1);
88881
+ var import_node_path14 = __toESM(require("node:path"), 1);
88649
88882
  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");
88883
+ if (platform === "win32" && env.APPDATA) return import_node_path14.default.join(env.APPDATA, "ryugi", "credentials.json");
88884
+ return import_node_path14.default.join(env.XDG_CONFIG_HOME || import_node_path14.default.join(home, ".config"), "ryugi", "credentials.json");
88652
88885
  }
88653
88886
  function readCredentials(file, origin) {
88654
88887
  let raw;
88655
88888
  try {
88656
- raw = JSON.parse(import_node_fs13.default.readFileSync(file, "utf8"));
88889
+ raw = JSON.parse(import_node_fs14.default.readFileSync(file, "utf8"));
88657
88890
  } catch {
88658
88891
  return null;
88659
88892
  }
@@ -88664,11 +88897,11 @@ function readCredentials(file, origin) {
88664
88897
  return { origin: savedOrigin, token, login };
88665
88898
  }
88666
88899
  function writeCredentials(file, credentials) {
88667
- import_node_fs13.default.mkdirSync(import_node_path13.default.dirname(file), { recursive: true, mode: 448 });
88900
+ import_node_fs14.default.mkdirSync(import_node_path14.default.dirname(file), { recursive: true, mode: 448 });
88668
88901
  writeJsonAtomic(file, credentials);
88669
88902
  }
88670
88903
  function clearCredentials(file) {
88671
- import_node_fs13.default.rmSync(file, { force: true });
88904
+ import_node_fs14.default.rmSync(file, { force: true });
88672
88905
  }
88673
88906
 
88674
88907
  // node_modules/.pnpm/ws@8.18.3/node_modules/ws/wrapper.mjs
@@ -88796,7 +89029,7 @@ function createRelayClient(options) {
88796
89029
  }
88797
89030
 
88798
89031
  // src/server/ws.ts
88799
- var import_node_crypto3 = __toESM(require("node:crypto"), 1);
89032
+ var import_node_crypto4 = __toESM(require("node:crypto"), 1);
88800
89033
  var WS_MAGIC = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
88801
89034
  var OPCODE_TEXT = 1;
88802
89035
  var OPCODE_CLOSE = 8;
@@ -88804,7 +89037,7 @@ var OPCODE_PING = 9;
88804
89037
  var OPCODE_PONG = 10;
88805
89038
  var MAX_CLIENT_FRAME = 8 * 1024 * 1024;
88806
89039
  function acceptKey(clientKey) {
88807
- return import_node_crypto3.default.createHash("sha1").update(clientKey + WS_MAGIC).digest("base64");
89040
+ return import_node_crypto4.default.createHash("sha1").update(clientKey + WS_MAGIC).digest("base64");
88808
89041
  }
88809
89042
  function encodeFrame(opcode, payload) {
88810
89043
  const len = payload.length;
@@ -88929,21 +89162,21 @@ function createDirectRelay(options) {
88929
89162
  }
88930
89163
 
88931
89164
  // 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);
89165
+ var import_node_fs15 = __toESM(require("node:fs"), 1);
89166
+ var import_node_path15 = __toESM(require("node:path"), 1);
88934
89167
  function hostIdFor(credentialsFile2) {
88935
- const file = import_node_path14.default.join(import_node_path14.default.dirname(credentialsFile2), "host-id");
89168
+ const file = import_node_path15.default.join(import_node_path15.default.dirname(credentialsFile2), "host-id");
88936
89169
  const saved = readPersisted(file);
88937
89170
  if (/^[A-Za-z0-9_-]{16,64}$/.test(saved)) return saved;
88938
89171
  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}
89172
+ import_node_fs15.default.mkdirSync(import_node_path15.default.dirname(file), { recursive: true, mode: 448 });
89173
+ import_node_fs15.default.writeFileSync(file, `${id2}
88941
89174
  `, { mode: 384 });
88942
89175
  return id2;
88943
89176
  }
88944
89177
 
88945
89178
  // src/server/avatars.ts
88946
- var import_node_crypto4 = require("node:crypto");
89179
+ var import_node_crypto5 = require("node:crypto");
88947
89180
  var AVATAR_ROUTE = /^\/api\/avatar\/([a-z]+)\/([^/]+)$/;
88948
89181
  var MAX_BYTES = 512 * 1024;
88949
89182
  var TIMEOUT_MS = 5e3;
@@ -88952,7 +89185,7 @@ var SOURCES = {
88952
89185
  gravatar: { valid: /^[0-9a-f]{64}$/, url: (id2) => `https://gravatar.com/avatar/${id2}?s=64&d=404` }
88953
89186
  };
88954
89187
  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")}`;
89188
+ var gravatarAvatar = (email) => `/api/avatar/gravatar/${(0, import_node_crypto5.createHash)("sha256").update(email.trim().toLowerCase()).digest("hex")}`;
88956
89189
  function createAvatars(fetchImpl = fetch) {
88957
89190
  const cache = /* @__PURE__ */ new Map();
88958
89191
  const fetchAvatar = async (url) => {
@@ -88984,7 +89217,7 @@ function createAvatars(fetchImpl = fetch) {
88984
89217
  }
88985
89218
 
88986
89219
  // src/server/cloud.ts
88987
- var import_node_crypto5 = __toESM(require("node:crypto"), 1);
89220
+ var import_node_crypto6 = __toESM(require("node:crypto"), 1);
88988
89221
 
88989
89222
  // src/shared/brand.ts
88990
89223
  var RYUGI_MARK_VIEWBOX = "0 0 512 512";
@@ -89094,32 +89327,32 @@ function callbackPage(result) {
89094
89327
  }
89095
89328
 
89096
89329
  // src/server/pack.ts
89097
- var import_node_fs15 = __toESM(require("node:fs"), 1);
89098
- var import_node_path15 = __toESM(require("node:path"), 1);
89330
+ var import_node_fs16 = __toESM(require("node:fs"), 1);
89331
+ var import_node_path16 = __toESM(require("node:path"), 1);
89099
89332
  var MAX_ARTIFACT_BYTES = 20 * 1024 * 1024;
89100
89333
  function formatBytes(bytes) {
89101
89334
  if (bytes < 1024) return `${bytes} B`;
89102
89335
  if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
89103
89336
  return `${(bytes / 1024 / 1024).toFixed(1)} MB`;
89104
89337
  }
89105
- var excluded = (type, relative) => type === "document" && relative === "comments.json";
89338
+ var excluded = (type, relative) => (type === "document" || type === "artboard") && relative === "comments.json";
89106
89339
  function packArtifact(dir, type, { maxBytes = MAX_ARTIFACT_BYTES } = {}) {
89107
- const rootReal = import_node_fs15.default.realpathSync(dir);
89340
+ const rootReal = import_node_fs16.default.realpathSync(dir);
89108
89341
  const found = [];
89109
89342
  const visited = /* @__PURE__ */ new Set([rootReal]);
89110
89343
  let total = 0;
89111
89344
  const walk = (current) => {
89112
- for (const entry of import_node_fs15.default.readdirSync(current, { withFileTypes: true })) {
89345
+ for (const entry of import_node_fs16.default.readdirSync(current, { withFileTypes: true })) {
89113
89346
  if (entry.name.startsWith(".")) continue;
89114
- const full = import_node_path15.default.join(current, entry.name);
89347
+ const full = import_node_path16.default.join(current, entry.name);
89115
89348
  let real;
89116
89349
  try {
89117
- real = import_node_fs15.default.realpathSync(full);
89350
+ real = import_node_fs16.default.realpathSync(full);
89118
89351
  } catch {
89119
89352
  continue;
89120
89353
  }
89121
- if (!real.startsWith(rootReal + import_node_path15.default.sep)) continue;
89122
- const stat = import_node_fs15.default.statSync(real);
89354
+ if (!real.startsWith(rootReal + import_node_path16.default.sep)) continue;
89355
+ const stat = import_node_fs16.default.statSync(real);
89123
89356
  if (stat.isDirectory()) {
89124
89357
  if (visited.has(real)) continue;
89125
89358
  visited.add(real);
@@ -89127,7 +89360,7 @@ function packArtifact(dir, type, { maxBytes = MAX_ARTIFACT_BYTES } = {}) {
89127
89360
  continue;
89128
89361
  }
89129
89362
  if (!stat.isFile()) continue;
89130
- const relative = import_node_path15.default.relative(dir, full).split(import_node_path15.default.sep).join("/");
89363
+ const relative = import_node_path16.default.relative(dir, full).split(import_node_path16.default.sep).join("/");
89131
89364
  if (excluded(type, relative)) continue;
89132
89365
  total += stat.size;
89133
89366
  found.push({ path: relative, real });
@@ -89137,12 +89370,12 @@ function packArtifact(dir, type, { maxBytes = MAX_ARTIFACT_BYTES } = {}) {
89137
89370
  if (total > maxBytes) {
89138
89371
  throw new Error(`artifact is ${formatBytes(total)}; the limit is ${formatBytes(maxBytes)}`);
89139
89372
  }
89140
- return found.map((file) => ({ path: file.path, data: import_node_fs15.default.readFileSync(file.real) }));
89373
+ return found.map((file) => ({ path: file.path, data: import_node_fs16.default.readFileSync(file.real) }));
89141
89374
  }
89142
89375
 
89143
89376
  // 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);
89377
+ var import_node_fs17 = __toESM(require("node:fs"), 1);
89378
+ var import_node_path17 = __toESM(require("node:path"), 1);
89146
89379
  var CLOUD_RUNTIME_URL = "/_ryugi/runtime/ryugi-ui.js";
89147
89380
  function sharedComments(file) {
89148
89381
  return {
@@ -89155,7 +89388,7 @@ function publishPayload(ref, dir, title, payload, comments = emptyComments()) {
89155
89388
  const { canvas, layout, versions } = payload;
89156
89389
  return { canvas, layout, versions };
89157
89390
  }
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) };
89391
+ 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
89392
  return { title };
89160
89393
  }
89161
89394
  var isPage = (type, file) => type === "html" ? file === "index.html" : (type === "document" || type === "artboard") && file.endsWith(".html");
@@ -89166,12 +89399,12 @@ function withCloudRuntime(type, files) {
89166
89399
  }
89167
89400
 
89168
89401
  // 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");
89402
+ var import_node_fs18 = __toESM(require("node:fs"), 1);
89403
+ var import_node_path18 = __toESM(require("node:path"), 1);
89404
+ var settingsFileFor = (credentialsFile2) => import_node_path18.default.join(import_node_path18.default.dirname(credentialsFile2), "config.json");
89172
89405
  function readSettings(file) {
89173
89406
  try {
89174
- const raw = JSON.parse(import_node_fs17.default.readFileSync(file, "utf8"));
89407
+ const raw = JSON.parse(import_node_fs18.default.readFileSync(file, "utf8"));
89175
89408
  return typeof raw === "object" && raw !== null && !Array.isArray(raw) ? raw : {};
89176
89409
  } catch {
89177
89410
  return {};
@@ -89182,7 +89415,7 @@ function readGlobalAutoPublish(file) {
89182
89415
  return typeof value === "boolean" ? value : void 0;
89183
89416
  }
89184
89417
  function writeGlobalAutoPublish(file, value) {
89185
- import_node_fs17.default.mkdirSync(import_node_path17.default.dirname(file), { recursive: true, mode: 448 });
89418
+ import_node_fs18.default.mkdirSync(import_node_path18.default.dirname(file), { recursive: true, mode: 448 });
89186
89419
  writeJsonAtomic(file, { ...readSettings(file), autoPublish: value });
89187
89420
  }
89188
89421
  function defaultGlobalAutoPublish(file) {
@@ -89254,10 +89487,10 @@ function createCloudRoutes(options) {
89254
89487
  const state = randomToken();
89255
89488
  const verifier = randomToken();
89256
89489
  pendingStates.set(state, { expires: now + LOGIN_STATE_TTL_MS, verifier });
89257
- const challenge = import_node_crypto5.default.createHash("sha256").update(verifier).digest("base64url");
89490
+ const challenge = import_node_crypto6.default.createHash("sha256").update(verifier).digest("base64url");
89258
89491
  return `${origin}/auth/cli?port=${options.port()}&state=${state}&challenge=${challenge}`;
89259
89492
  };
89260
- const cloudFetch = async (method, path21, body) => {
89493
+ const cloudFetch = async (method, path22, body) => {
89261
89494
  const credentials = readCredentials(credentialsFile2, origin);
89262
89495
  if (!credentials) throw new SignedOut();
89263
89496
  const headers = { Authorization: `Bearer ${credentials.token}` };
@@ -89271,7 +89504,7 @@ function createCloudRoutes(options) {
89271
89504
  }
89272
89505
  let res;
89273
89506
  try {
89274
- res = await fetch(`${origin}${path21}`, { method, headers, body: payload });
89507
+ res = await fetch(`${origin}${path22}`, { method, headers, body: payload });
89275
89508
  } catch (err) {
89276
89509
  throw new CloudRefused(502, `could not reach ${origin}: ${err.message}`);
89277
89510
  }
@@ -89328,9 +89561,9 @@ function createCloudRoutes(options) {
89328
89561
  }
89329
89562
  const key2 = `${ref.type}/${ref.id}`;
89330
89563
  const entry = artifactEntry(key2);
89331
- const hashed = files.map((file) => ({ ...file, hash: import_node_crypto5.default.createHash("sha256").update(file.data).digest("hex") }));
89564
+ const hashed = files.map((file) => ({ ...file, hash: import_node_crypto6.default.createHash("sha256").update(file.data).digest("hex") }));
89332
89565
  const byHash = new Map(hashed.map((file) => [file.hash, file.data]));
89333
- const manifest = hashed.map(({ path: path21, hash }) => ({ path: path21, hash }));
89566
+ const manifest = hashed.map(({ path: path22, hash }) => ({ path: path22, hash }));
89334
89567
  const version2 = (visibility) => ({ title: inspected.title, payload: inspected.payload, files: manifest, project, ...visibility ? { visibility } : {} });
89335
89568
  const create3 = () => createRecord2(ref, requested ?? entry.visibility ?? "private");
89336
89569
  const upload = async (id2, hashes) => {
@@ -89553,7 +89786,7 @@ function createCloudRoutes(options) {
89553
89786
 
89554
89787
  // src/server/comment-names.ts
89555
89788
  var import_node_child_process = require("node:child_process");
89556
- var import_node_fs18 = __toESM(require("node:fs"), 1);
89789
+ var import_node_fs19 = __toESM(require("node:fs"), 1);
89557
89790
 
89558
89791
  // src/server/agent.ts
89559
89792
  var NAMES = {
@@ -89634,7 +89867,7 @@ function commentNames({ agentFile, projectDir, cloud }) {
89634
89867
  },
89635
89868
  agent: () => {
89636
89869
  try {
89637
- return detectAgent(JSON.parse(import_node_fs18.default.readFileSync(agentFile, "utf8")));
89870
+ return detectAgent(JSON.parse(import_node_fs19.default.readFileSync(agentFile, "utf8")));
89638
89871
  } catch {
89639
89872
  return void 0;
89640
89873
  }
@@ -89662,8 +89895,8 @@ function viewerPage(template, page) {
89662
89895
  }
89663
89896
 
89664
89897
  // 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);
89898
+ var import_node_crypto7 = __toESM(require("node:crypto"), 1);
89899
+ var import_node_fs20 = __toESM(require("node:fs"), 1);
89667
89900
  var SIGNED_OUT_MESSAGE = "auto-publish paused: the Ryugi Cloud sign-in expired";
89668
89901
  function isNotice(value) {
89669
89902
  if (typeof value !== "object" || value === null) return false;
@@ -89673,7 +89906,7 @@ function isNotice(value) {
89673
89906
  function readAutoPublishState(file) {
89674
89907
  let raw = {};
89675
89908
  try {
89676
- const parsed = JSON.parse(import_node_fs19.default.readFileSync(file, "utf8"));
89909
+ const parsed = JSON.parse(import_node_fs20.default.readFileSync(file, "utf8"));
89677
89910
  if (typeof parsed === "object" && parsed !== null) raw = parsed;
89678
89911
  } catch {
89679
89912
  }
@@ -89693,7 +89926,7 @@ function createAutoPublisher(options) {
89693
89926
  const remarked = /* @__PURE__ */ new Set();
89694
89927
  const save = () => writeJsonAtomic(options.stateFile, { dirty: [...dirty], notice, paused });
89695
89928
  const raise = (kind, message) => {
89696
- if (notice?.kind !== kind) notice = { id: import_node_crypto6.default.randomUUID(), kind, message };
89929
+ if (notice?.kind !== kind) notice = { id: import_node_crypto7.default.randomUUID(), kind, message };
89697
89930
  };
89698
89931
  const run2 = async () => {
89699
89932
  if (!options.enabled() || dirty.size === 0) return;
@@ -89753,7 +89986,7 @@ function createAutoPublisher(options) {
89753
89986
  }
89754
89987
 
89755
89988
  // src/server/artifact-file.ts
89756
- var import_node_fs20 = __toESM(require("node:fs"), 1);
89989
+ var import_node_fs21 = __toESM(require("node:fs"), 1);
89757
89990
  var RESOLVE = { artboard: resolveBoardFile, document: resolveDocumentAsset, html: resolveHtmlFile };
89758
89991
  var isArtifactPage = (type, relative) => type === "html" ? relative === "index.html" : relative.endsWith(".html");
89759
89992
  function readArtifactFile(dir, type, relative, uiRuntimeUrl) {
@@ -89762,7 +89995,7 @@ function readArtifactFile(dir, type, relative, uiRuntimeUrl) {
89762
89995
  if (!target) return null;
89763
89996
  let data;
89764
89997
  try {
89765
- data = import_node_fs20.default.readFileSync(target);
89998
+ data = import_node_fs21.default.readFileSync(target);
89766
89999
  } catch {
89767
90000
  return null;
89768
90001
  }
@@ -89772,25 +90005,25 @@ function readArtifactFile(dir, type, relative, uiRuntimeUrl) {
89772
90005
  }
89773
90006
 
89774
90007
  // 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("/");
90008
+ var import_node_fs22 = __toESM(require("node:fs"), 1);
90009
+ var import_node_path19 = __toESM(require("node:path"), 1);
90010
+ var toPosix = (file) => file.split(import_node_path19.default.sep).join("/");
89778
90011
  var ignored = (file) => file === ".state" || file.startsWith(".state/");
89779
90012
  function snapshot(root, dir = root, out = /* @__PURE__ */ new Map()) {
89780
90013
  let entries2;
89781
90014
  try {
89782
- entries2 = import_node_fs21.default.readdirSync(dir, { withFileTypes: true });
90015
+ entries2 = import_node_fs22.default.readdirSync(dir, { withFileTypes: true });
89783
90016
  } catch {
89784
90017
  return out;
89785
90018
  }
89786
90019
  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));
90020
+ const full = import_node_path19.default.join(dir, entry.name);
90021
+ const rel = toPosix(import_node_path19.default.relative(root, full));
89789
90022
  if (ignored(rel)) continue;
89790
90023
  if (entry.isDirectory()) snapshot(root, full, out);
89791
90024
  else {
89792
90025
  try {
89793
- out.set(rel, import_node_fs21.default.statSync(full).mtimeMs);
90026
+ out.set(rel, import_node_fs22.default.statSync(full).mtimeMs);
89794
90027
  } catch {
89795
90028
  }
89796
90029
  }
@@ -89823,7 +90056,7 @@ function watchTree(root, onChange, debounceMs = 120, backupPollMs = 1e3) {
89823
90056
  }, debounceMs);
89824
90057
  };
89825
90058
  try {
89826
- const watcher = import_node_fs21.default.watch(root, { recursive: true }, (_event, filename) => {
90059
+ const watcher = import_node_fs22.default.watch(root, { recursive: true }, (_event, filename) => {
89827
90060
  if (filename) queue([toPosix(filename.toString())]);
89828
90061
  });
89829
90062
  watcher.on("error", () => {
@@ -89873,6 +90106,8 @@ var API_PREVIEW_ROUTE = /^\/api\/preview\/([^/]+)\/([^/]+)$/;
89873
90106
  var API_DOCUMENT_ROUTE = /^\/api\/document\/([^/]+)\/(state|update|comments)$/;
89874
90107
  var MAX_COMMENT_OP = 64 * 1024;
89875
90108
  var API_AGENT_COMMENTS_ROUTE = /^\/api\/agent\/document\/([^/]+)\/comments$/;
90109
+ var API_BOARD_COMMENTS_ROUTE = /^\/api\/artboard\/([^/]+)\/comments$/;
90110
+ var API_AGENT_BOARD_COMMENTS_ROUTE = /^\/api\/agent\/artboard\/([^/]+)\/comments$/;
89876
90111
  var MAX_DOCUMENT_UPDATE = 8 * 1024 * 1024;
89877
90112
  var MAX_RELAY_FILE = 8 * 1024 * 1024;
89878
90113
  var MAX_RUNTIME_MESSAGE = 2048;
@@ -89891,17 +90126,17 @@ function titleOf(payload, ref) {
89891
90126
  return payload.canvas?.title || ref.id;
89892
90127
  }
89893
90128
  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");
90129
+ const root = import_node_path20.default.resolve(options.root);
90130
+ const viewerDir = import_node_path20.default.resolve(options.viewerDir);
90131
+ const runtimeDir = options.runtimeDir ? import_node_path20.default.resolve(options.runtimeDir) : null;
90132
+ const stateDir = import_node_path20.default.join(root, ".state");
90133
+ import_node_fs23.default.mkdirSync(stateDir, { recursive: true, mode: 448 });
90134
+ import_node_fs23.default.writeFileSync(import_node_path20.default.join(stateDir, ".gitignore"), "*\n");
89900
90135
  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");
90136
+ const portFile = import_node_path20.default.join(stateDir, ".last-port");
90137
+ const tokenFile = import_node_path20.default.join(stateDir, ".last-token");
89903
90138
  const token = readPersisted(tokenFile) || randomToken();
89904
- import_node_fs22.default.writeFileSync(tokenFile, `${token}
90139
+ import_node_fs23.default.writeFileSync(tokenFile, `${token}
89905
90140
  `, { mode: 384 });
89906
90141
  const contentToken = randomToken();
89907
90142
  const runtimeWarnings = /* @__PURE__ */ new Map();
@@ -89931,7 +90166,8 @@ async function startArtifactsServer(options) {
89931
90166
  direct.emit(message.artifact, message);
89932
90167
  };
89933
90168
  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 });
90169
+ const names = commentNames({ agentFile: import_node_path20.default.join(stateDir, "agent"), projectDir: import_node_path20.default.resolve(root, "../.."), cloud: options.cloud });
90170
+ const boardComments = createBoardCommentRegistry({ root, stateDir, broadcast, names });
89935
90171
  const documents = createDocumentRegistry({
89936
90172
  root,
89937
90173
  stateDir,
@@ -89969,8 +90205,8 @@ async function startArtifactsServer(options) {
89969
90205
  }
89970
90206
  return { dir, title, reasons, payload: published };
89971
90207
  };
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;
90208
+ 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;
90209
+ 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
90210
  const sameOrigin = (req) => {
89975
90211
  const origin2 = req.headers.origin;
89976
90212
  const host = req.headers.host ?? "";
@@ -90049,7 +90285,7 @@ async function startArtifactsServer(options) {
90049
90285
  };
90050
90286
  const blockIdsOf = async (id2, dir) => {
90051
90287
  await (await documents.open(id2))?.flush();
90052
- const state = parseDocument2(import_node_fs22.default.readFileSync(import_node_path19.default.join(dir, "doc.xml"), "utf8"));
90288
+ const state = parseDocument2(import_node_fs23.default.readFileSync(import_node_path20.default.join(dir, "doc.xml"), "utf8"));
90053
90289
  return new Set(state.root.children.flatMap((block) => block.$?.blockId ? [block.$.blockId] : []));
90054
90290
  };
90055
90291
  const serveAgentComments = async (req, res, id2) => {
@@ -90089,6 +90325,55 @@ async function startArtifactsServer(options) {
90089
90325
  sendJson(res, 400, { error: err.message });
90090
90326
  }
90091
90327
  };
90328
+ const serveBoardComments = async (req, res, id2) => {
90329
+ if (req.method !== "POST" || !sameOrigin(req)) {
90330
+ send(res, 403, "Forbidden");
90331
+ return;
90332
+ }
90333
+ if (!resolveArtifactDir(root, "artboard", id2)) {
90334
+ sendJson(res, 404, { error: `artboard/${id2} not found` });
90335
+ return;
90336
+ }
90337
+ const op = asBoardHumanOp(await readCommentBody(req));
90338
+ if (!op) {
90339
+ sendJson(res, 400, { error: "not a comment op" });
90340
+ return;
90341
+ }
90342
+ sendJson(res, 200, boardComments.apply(id2, op));
90343
+ };
90344
+ const serveAgentBoardComments = async (req, res, id2) => {
90345
+ const dir = resolveArtifactDir(root, "artboard", id2);
90346
+ if (!dir) {
90347
+ sendJson(res, 404, { error: `artboard/${id2} not found` });
90348
+ return;
90349
+ }
90350
+ const body = await readCommentBody(req);
90351
+ const fields = typeof body === "object" && body !== null ? body : {};
90352
+ const agent = typeof fields.agent === "string" && fields.agent.length > 0 && fields.agent.length <= 100 ? fields.agent : void 0;
90353
+ if (fields.op === "list") {
90354
+ sendJson(res, 200, { type: "comments", artifact: `artboard/${id2}`, threads: boardComments.list(id2, agent ?? "Agent", fields.all === true) });
90355
+ return;
90356
+ }
90357
+ const shorthand = agentAddOnBoard(body, boardSpecs(dir));
90358
+ if (shorthand && "error" in shorthand) {
90359
+ sendJson(res, 400, { error: `${shorthand.error} in artboard/${id2}` });
90360
+ return;
90361
+ }
90362
+ const op = shorthand ?? asBoardAgentOp(body);
90363
+ if (!op) {
90364
+ 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" });
90365
+ return;
90366
+ }
90367
+ if (op.op === "add" && op.anchor.board && !(op.anchor.board in boardFiles(dir))) {
90368
+ sendJson(res, 400, { error: `no board ${op.anchor.board} in artboard/${id2}` });
90369
+ return;
90370
+ }
90371
+ try {
90372
+ sendJson(res, 200, boardComments.applyAgent(id2, op, agent));
90373
+ } catch (err) {
90374
+ sendJson(res, 400, { error: err.message });
90375
+ }
90376
+ };
90092
90377
  const isAuthorized = (req, keyFromQuery) => keyFromQuery !== null && safeEqual(keyFromQuery, token) || safeEqual(parseCookies(req.headers.cookie)[cookieName] ?? "", token);
90093
90378
  const serveContent = (res, rest) => {
90094
90379
  if (rest.startsWith("runtime/")) {
@@ -90129,7 +90414,7 @@ async function startArtifactsServer(options) {
90129
90414
  sendFile(res, target, headers);
90130
90415
  return;
90131
90416
  }
90132
- import_node_fs22.default.readFile(target, "utf8", (err, source) => {
90417
+ import_node_fs23.default.readFile(target, "utf8", (err, source) => {
90133
90418
  if (err) send(res, 404, "Not found");
90134
90419
  else {
90135
90420
  const uiRuntimeUrl = runtimeDir ? `${contentPrefix}runtime/ryugi-ui.js` : void 0;
@@ -90140,6 +90425,7 @@ async function startArtifactsServer(options) {
90140
90425
  const artifactMeta = (ref, dir, contentOrigin = "") => ({
90141
90426
  ...readArtifact(dir, ref, runtimeOf(ref, dir)),
90142
90427
  ...ref.type !== "html" ? { agent: names.agent() } : {},
90428
+ ...ref.type === "artboard" ? { comments: boardComments.comments(ref.id), staleThreads: boardComments.stale(ref.id) } : {},
90143
90429
  contentBase: `${contentOrigin}${contentPrefix}${ref.type}/${ref.id}/`
90144
90430
  });
90145
90431
  const serveArtifactApi = (res, type, id2) => {
@@ -90158,7 +90444,7 @@ async function startArtifactsServer(options) {
90158
90444
  title: titleOf(readArtifact(entry.dir, entry), entry),
90159
90445
  updatedAt: latestMtime(entry.dir)
90160
90446
  }));
90161
- sendJson(res, 200, { project: import_node_path19.default.basename(import_node_path19.default.dirname(import_node_path19.default.dirname(root))), artifacts: summaries, warnings });
90447
+ sendJson(res, 200, { project: import_node_path20.default.basename(import_node_path20.default.dirname(import_node_path20.default.dirname(root))), artifacts: summaries, warnings });
90162
90448
  };
90163
90449
  function onClientText(socket, text2) {
90164
90450
  let message;
@@ -90220,7 +90506,7 @@ async function startArtifactsServer(options) {
90220
90506
  return;
90221
90507
  }
90222
90508
  if (pathname === "/robots.txt") {
90223
- sendFile(res, import_node_path19.default.join(viewerDir, "robots.txt"));
90509
+ sendFile(res, import_node_path20.default.join(viewerDir, "robots.txt"));
90224
90510
  return;
90225
90511
  }
90226
90512
  if (pathname.startsWith("/c/")) {
@@ -90302,6 +90588,24 @@ async function startArtifactsServer(options) {
90302
90588
  });
90303
90589
  return;
90304
90590
  }
90591
+ const agentBoardMatch = API_AGENT_BOARD_COMMENTS_ROUTE.exec(pathname);
90592
+ if (agentBoardMatch) {
90593
+ if (req.method !== "POST" || key2 === null || !safeEqual(key2, token)) {
90594
+ send(res, 403, "Forbidden: agent calls need ?key=");
90595
+ return;
90596
+ }
90597
+ void serveAgentBoardComments(req, res, agentBoardMatch[1]).catch((err) => {
90598
+ if (!res.headersSent) send(res, 500, `Artifacts error: ${err.message}`);
90599
+ });
90600
+ return;
90601
+ }
90602
+ const boardCommentsMatch = API_BOARD_COMMENTS_ROUTE.exec(pathname);
90603
+ if (boardCommentsMatch) {
90604
+ void serveBoardComments(req, res, boardCommentsMatch[1]).catch((err) => {
90605
+ if (!res.headersSent) send(res, 500, `Artifacts error: ${err.message}`);
90606
+ });
90607
+ return;
90608
+ }
90305
90609
  const documentMatch = API_DOCUMENT_ROUTE.exec(pathname);
90306
90610
  if (documentMatch) {
90307
90611
  void serveDocumentApi(req, res, documentMatch[1], documentMatch[2]).catch((err) => {
@@ -90317,7 +90621,7 @@ async function startArtifactsServer(options) {
90317
90621
  if (SHELL_ROUTE.test(pathname) || PREVIEW_ROUTE.test(pathname) || pathname === "/index.html") {
90318
90622
  const ref = artifactKeyOf(pathname.replace(/^\/(?:preview\/)?/, "").replace(/\/$/, ""));
90319
90623
  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)));
90624
+ const project = import_node_path20.default.basename(import_node_path20.default.dirname(import_node_path20.default.dirname(root)));
90321
90625
  const title = ref ? dir ? titleOf(readArtifact(dir, ref), ref) : ref.id : `${project} \xB7 Artifacts`;
90322
90626
  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
90627
  const canonical = `http://${urlHostForHttp(options.urlHost)}:${port}/${ref ? `${ref.type}/${ref.id}` : ""}`;
@@ -90326,7 +90630,7 @@ async function startArtifactsServer(options) {
90326
90630
  "Content-Security-Policy": PREVIEW_ROUTE.test(pathname) ? VIEWER_CSP.replace("frame-ancestors 'none'", "frame-ancestors 'self'") : VIEWER_CSP
90327
90631
  };
90328
90632
  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"));
90633
+ 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
90634
  return;
90331
90635
  }
90332
90636
  const target = resolveInside(viewerDir, pathname.slice(1));
@@ -90385,7 +90689,7 @@ async function startArtifactsServer(options) {
90385
90689
  port = await listen(server, options.host, 0);
90386
90690
  }
90387
90691
  cookieName = `ryugi_artifacts_${port}`;
90388
- import_node_fs22.default.writeFileSync(portFile, `${port}
90692
+ import_node_fs23.default.writeFileSync(portFile, `${port}
90389
90693
  `, { mode: 384 });
90390
90694
  const origin = `http://${urlHostForHttp(options.urlHost)}:${port}`;
90391
90695
  const url = `${origin}/?key=${token}`;
@@ -90402,7 +90706,7 @@ async function startArtifactsServer(options) {
90402
90706
  return payload;
90403
90707
  };
90404
90708
  const refreshState = (ref, dir) => {
90405
- signatures.set(`${ref.type}/${ref.id}`, artifactSignature(dir));
90709
+ signatures.set(`${ref.type}/${ref.id}`, artifactSignature(dir, ref.type));
90406
90710
  return writeState(ref, dir);
90407
90711
  };
90408
90712
  const initialChanges = [];
@@ -90411,7 +90715,7 @@ async function startArtifactsServer(options) {
90411
90715
  initialChanges.push({ ref: entry, payload, files: [] });
90412
90716
  if (entry.type === "document") void documents.open(entry.id);
90413
90717
  }
90414
- writeJsonAtomic(import_node_path19.default.join(stateDir, "server-info"), { type: "server-started", url, port, root });
90718
+ writeJsonAtomic(import_node_path20.default.join(stateDir, "server-info"), { type: "server-started", url, port, root });
90415
90719
  const relayViewers = /* @__PURE__ */ new Map();
90416
90720
  const servedArtifacts = () => {
90417
90721
  const served = /* @__PURE__ */ new Map();
@@ -90444,6 +90748,11 @@ async function startArtifactsServer(options) {
90444
90748
  if (typeof request.message === "object" && request.message !== null) acceptReport({ ...request.message, artifact: key2 });
90445
90749
  return null;
90446
90750
  }
90751
+ if (request.op === "comment" && ref.type === "artboard") {
90752
+ const op = JSON.stringify(request.comment ?? null).length <= MAX_COMMENT_OP ? asBoardHumanOp(request.comment) : null;
90753
+ if (!op) throw new Error("not a comment op");
90754
+ return boardComments.apply(ref.id, op);
90755
+ }
90447
90756
  if ((request.op === "state" || request.op === "update" || request.op === "comment") && ref.type !== "document") throw new Error(`${key2} is not a document`);
90448
90757
  if (request.op === "state" || request.op === "update") {
90449
90758
  const session = await documents.open(ref.id);
@@ -90508,7 +90817,9 @@ async function startArtifactsServer(options) {
90508
90817
  }
90509
90818
  if (ref.type === "document" && (changed.includes("doc.xml") || changed.length === 0)) documents.fileChanged(ref.id);
90510
90819
  if (ref.type === "document" && changed.includes("comments.json")) documents.commentsChanged(ref.id);
90511
- if (signatures.get(key2) === artifactSignature(dir)) continue;
90820
+ if (ref.type === "artboard" && changed.includes("comments.json")) boardComments.sync(ref.id);
90821
+ if (ref.type === "artboard" && (changed.some((file) => file !== "comments.json") || changed.length === 0)) boardComments.boardsChanged(ref.id);
90822
+ if (signatures.get(key2) === artifactSignature(dir, ref.type)) continue;
90512
90823
  autoPublisher?.changed(key2);
90513
90824
  runtimeWarnings.delete(key2);
90514
90825
  const payload = refreshState(ref, dir);
@@ -90552,7 +90863,7 @@ async function startArtifactsServer(options) {
90552
90863
  }
90553
90864
 
90554
90865
  // src/server/lifecycle.ts
90555
- var import_node_fs23 = __toESM(require("node:fs"), 1);
90866
+ var import_node_fs24 = __toESM(require("node:fs"), 1);
90556
90867
  var DEFAULT_IDLE_TIMEOUT_MS = 4 * 60 * 60 * 1e3;
90557
90868
  var DEFAULT_LIFECYCLE_CHECK_MS = 60 * 1e3;
90558
90869
  var SERVER_ID_ARG = "--artifacts-server-id=";
@@ -90584,7 +90895,7 @@ function parseEnv(env, argv = []) {
90584
90895
  function readOwners(file) {
90585
90896
  let text2;
90586
90897
  try {
90587
- text2 = import_node_fs23.default.readFileSync(file, "utf8");
90898
+ text2 = import_node_fs24.default.readFileSync(file, "utf8");
90588
90899
  } catch {
90589
90900
  return [];
90590
90901
  }
@@ -90647,20 +90958,20 @@ function openReady(payload) {
90647
90958
  }
90648
90959
 
90649
90960
  // package.json
90650
- var version = "1.15.1";
90961
+ var version = "1.15.2";
90651
90962
 
90652
90963
  // src/server/main.ts
90653
90964
  async function main() {
90654
90965
  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");
90966
+ const root = import_node_path21.default.resolve(config.root);
90967
+ const stateDir = import_node_path21.default.join(root, ".state");
90657
90968
  const server = await startArtifactsServer({
90658
90969
  root,
90659
- viewerDir: import_node_path20.default.join(__dirname, "viewer"),
90660
- runtimeDir: import_node_path20.default.join(__dirname, "runtime"),
90970
+ viewerDir: import_node_path21.default.join(__dirname, "viewer"),
90971
+ runtimeDir: import_node_path21.default.join(__dirname, "runtime"),
90661
90972
  host: config.host,
90662
90973
  urlHost: config.urlHost,
90663
- configFile: import_node_path20.default.join(import_node_path20.default.dirname(root), "config.json"),
90974
+ configFile: import_node_path21.default.join(import_node_path21.default.dirname(root), "config.json"),
90664
90975
  cloud: { origin: config.cloudOrigin, credentialsFile: credentialsFile(process.env, process.platform, import_node_os.default.homedir()) },
90665
90976
  relay: { url: `${config.cloudOrigin.replace(/^http/, "ws")}/api/relay/host`, version }
90666
90977
  });
@@ -90690,12 +91001,12 @@ async function main() {
90690
91001
  if (stopping) return;
90691
91002
  stopping = true;
90692
91003
  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() });
91004
+ import_node_fs25.default.rmSync(import_node_path21.default.join(stateDir, "server-info"), { force: true });
91005
+ writeJsonAtomic(import_node_path21.default.join(stateDir, "server-stopped"), { reason, timestamp: Date.now() });
90695
91006
  await server.close();
90696
91007
  process.exit(0);
90697
91008
  };
90698
- const ownersFile = import_node_path20.default.join(stateDir, "owners");
91009
+ const ownersFile = import_node_path21.default.join(stateDir, "owners");
90699
91010
  const timer = setInterval(() => {
90700
91011
  const owners = readOwners(ownersFile);
90701
91012
  const reason = shutdownReason({
@@ -90709,7 +91020,7 @@ async function main() {
90709
91020
  timer.unref();
90710
91021
  process.on("SIGTERM", () => void shutdown("SIGTERM"));
90711
91022
  process.on("SIGINT", () => void shutdown("SIGINT"));
90712
- import_node_fs24.default.rmSync(import_node_path20.default.join(stateDir, "server-stopped"), { force: true });
91023
+ import_node_fs25.default.rmSync(import_node_path21.default.join(stateDir, "server-stopped"), { force: true });
90713
91024
  console.log(JSON.stringify({ type: "server-started", url: server.url, port: server.port, root }));
90714
91025
  }
90715
91026
  main().catch((err) => {