ryugi-artifacts 1.15.0 → 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 +581 -258
  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-CKVtG8Wd.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);
@@ -18207,7 +18278,9 @@ function readRaw(boardDir) {
18207
18278
  }
18208
18279
  function boardVersions(boardDir, canvas) {
18209
18280
  const versions = {};
18210
- for (const board of Object.values(canvas.boards)) {
18281
+ let writing;
18282
+ let latestWrite = -Infinity;
18283
+ for (const [id2, board] of Object.entries(canvas.boards)) {
18211
18284
  const target = resolveBoardFile(boardDir, board.file);
18212
18285
  if (!target) {
18213
18286
  versions[board.file] = 0;
@@ -18215,12 +18288,19 @@ function boardVersions(boardDir, canvas) {
18215
18288
  }
18216
18289
  try {
18217
18290
  const stat = import_node_fs5.default.statSync(target);
18218
- versions[board.file] = stat.size > 0 ? stat.mtimeMs : 0;
18291
+ const html = import_node_fs5.default.readFileSync(target, "utf8");
18292
+ const document2 = /<(?:!doctype|html)\b/i.test(html);
18293
+ const ready = html.trim().length > 0 && (!document2 || /<\/html>\s*(?:<!--[\s\S]*?-->\s*)*$/i.test(html));
18294
+ versions[board.file] = ready ? stat.mtimeMs : 0;
18295
+ if (!ready && stat.mtimeMs >= latestWrite) {
18296
+ writing = id2;
18297
+ latestWrite = stat.mtimeMs;
18298
+ }
18219
18299
  } catch {
18220
18300
  versions[board.file] = 0;
18221
18301
  }
18222
18302
  }
18223
- return versions;
18303
+ return { versions, writing };
18224
18304
  }
18225
18305
  function readCanvas(boardDir) {
18226
18306
  const { raw, warning } = readRaw(boardDir);
@@ -18228,16 +18308,19 @@ function readCanvas(boardDir) {
18228
18308
  try {
18229
18309
  const structural = structuralWarnings(raw);
18230
18310
  if (structural.length > 0) return { canvas: null, warnings: structural, pending: [], layout: null, versions: {} };
18231
- const canvas = raw;
18311
+ const canvas = { ...raw };
18312
+ const { versions, writing } = boardVersions(boardDir, canvas);
18232
18313
  const pending = /* @__PURE__ */ new Set();
18233
- const warnings = validateCanvas(canvas, (rel) => resolveBoardFile(boardDir, rel) !== null, {
18234
- pending,
18235
- allowPending: canvas.building === true
18236
- });
18237
- const versions = boardVersions(boardDir, canvas);
18238
18314
  for (const [id2, board] of Object.entries(canvas.boards)) {
18239
18315
  if (!versions[board.file]) pending.add(id2);
18240
18316
  }
18317
+ canvas.building = pending.size > 0;
18318
+ delete canvas.writing;
18319
+ if (writing) canvas.writing = writing;
18320
+ const warnings = validateCanvas(canvas, (rel) => resolveBoardFile(boardDir, rel) !== null, {
18321
+ pending,
18322
+ allowPending: true
18323
+ });
18241
18324
  return { canvas, warnings, pending: [...pending].sort(), layout: computeLayout(canvas), versions };
18242
18325
  } catch (err) {
18243
18326
  return {
@@ -18270,7 +18353,31 @@ function writeArtifactState(stateDir, ref, payload, extra = {}) {
18270
18353
  });
18271
18354
  }
18272
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
+
18273
18379
  // src/server/document-comments.ts
18380
+ var import_node_fs7 = __toESM(require("node:fs"), 1);
18274
18381
  var MAX_TEXT = 2e4;
18275
18382
  var isText3 = (value, max = MAX_TEXT) => typeof value === "string" && value.length <= max;
18276
18383
  var isId = (value) => isText3(value, 100) && value.length > 0;
@@ -18307,20 +18414,30 @@ function asAgentOp(value) {
18307
18414
  return null;
18308
18415
  }
18309
18416
  }
18417
+ var documentRules = {
18418
+ empty: emptyComments,
18419
+ saved: savedComments,
18420
+ merge: mergeComments,
18421
+ human: applyHumanOp,
18422
+ agent: applyAgentOp
18423
+ };
18310
18424
  var noNames = { human: () => void 0, agent: () => void 0 };
18311
18425
  function createCommentStore(file, names = noNames, now = () => (/* @__PURE__ */ new Date()).toISOString(), newId = () => nanoid2(10)) {
18312
- 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();
18313
18430
  let lastSeen = null;
18314
18431
  let warnings = [];
18315
18432
  let loaded = false;
18316
18433
  const write = () => {
18317
18434
  writeJsonAtomic(file, current);
18318
- lastSeen = import_node_fs6.default.readFileSync(file, "utf8");
18435
+ lastSeen = import_node_fs7.default.readFileSync(file, "utf8");
18319
18436
  };
18320
18437
  const sync = () => {
18321
18438
  let text2;
18322
18439
  try {
18323
- text2 = import_node_fs6.default.readFileSync(file, "utf8");
18440
+ text2 = import_node_fs7.default.readFileSync(file, "utf8");
18324
18441
  } catch {
18325
18442
  return false;
18326
18443
  }
@@ -18328,8 +18445,8 @@ function createCommentStore(file, names = noNames, now = () => (/* @__PURE__ */
18328
18445
  lastSeen = text2;
18329
18446
  try {
18330
18447
  const incoming = parseComments(text2);
18331
- const base = loaded ? current : savedComments(incoming);
18332
- 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());
18333
18450
  current = merged.file;
18334
18451
  warnings = merged.warnings;
18335
18452
  if (`${JSON.stringify(current, null, 2)}
@@ -18347,13 +18464,13 @@ function createCommentStore(file, names = noNames, now = () => (/* @__PURE__ */
18347
18464
  sync,
18348
18465
  apply: (op) => {
18349
18466
  sync();
18350
- current = applyHumanOp(current, op, now(), newId, names.human());
18467
+ current = rules.human(current, op, now(), newId, names.human());
18351
18468
  write();
18352
18469
  return current;
18353
18470
  },
18354
18471
  applyAgent: (op, name) => {
18355
18472
  sync();
18356
- const result = applyAgentOp(current, op, now(), newId, name ?? names.agent());
18473
+ const result = rules.agent(current, op, now(), newId, name ?? names.agent());
18357
18474
  current = result.file;
18358
18475
  write();
18359
18476
  return result;
@@ -18361,38 +18478,166 @@ function createCommentStore(file, names = noNames, now = () => (/* @__PURE__ */
18361
18478
  };
18362
18479
  }
18363
18480
 
18364
- // src/server/documents.ts
18365
- var import_node_fs8 = __toESM(require("node:fs"), 1);
18366
- var import_node_path8 = __toESM(require("node:path"), 1);
18367
-
18368
- // src/server/comment-cursors.ts
18369
- var import_node_fs7 = __toESM(require("node:fs"), 1);
18370
- var import_node_path6 = __toESM(require("node:path"), 1);
18371
- var cursorsFile = (stateDir, id2) => import_node_path6.default.join(stateDir, "document", id2, "comment-cursors.json");
18372
- 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) {
18373
18512
  try {
18374
- const value = JSON.parse(import_node_fs7.default.readFileSync(file, "utf8"));
18375
- if (typeof value !== "object" || value === null || Array.isArray(value)) return {};
18376
- 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
+ ));
18377
18517
  } catch {
18378
18518
  return {};
18379
18519
  }
18380
18520
  }
18381
- 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
+ };
18382
18597
  return {
18383
- get: (id2, agent) => read(cursorsFile(stateDir, id2))[agent] ?? null,
18384
- set: (id2, agent, stamp) => {
18385
- const file = cursorsFile(stateDir, id2);
18386
- const all = read(file);
18387
- if (all[agent] === stamp) return;
18388
- 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);
18389
18630
  }
18390
18631
  };
18391
18632
  }
18392
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
+
18393
18638
  // src/server/document-session.ts
18394
18639
  var import_promises = __toESM(require("node:fs/promises"), 1);
18395
- var import_node_path7 = __toESM(require("node:path"), 1);
18640
+ var import_node_path8 = __toESM(require("node:path"), 1);
18396
18641
 
18397
18642
  // node_modules/.pnpm/lexical@0.52.0_typescript@5.9.3/node_modules/lexical/dist/Lexical.dev.js
18398
18643
  var Lexical_dev_exports = {};
@@ -19250,19 +19495,19 @@ var ElementDOMSlot = class _ElementDOMSlot extends DOMSlot {
19250
19495
  }
19251
19496
  };
19252
19497
  function indexPath(root, child) {
19253
- const path21 = [];
19498
+ const path22 = [];
19254
19499
  let node = child;
19255
19500
  for (; node !== root && node !== null; node = node.parentNode) {
19256
19501
  let i6 = 0;
19257
19502
  for (let sibling = node.previousSibling; sibling !== null; sibling = sibling.previousSibling) {
19258
19503
  i6++;
19259
19504
  }
19260
- path21.push(i6);
19505
+ path22.push(i6);
19261
19506
  }
19262
19507
  if (!(node === root)) {
19263
19508
  formatDevErrorMessage(`indexPath: root is not a parent of child`);
19264
19509
  }
19265
- return path21.reverse();
19510
+ return path22.reverse();
19266
19511
  }
19267
19512
  // @__NO_SIDE_EFFECTS__
19268
19513
  function readLexicalVersion() {
@@ -34042,9 +34287,9 @@ function getActiveElementDeep(root) {
34042
34287
  function getComposedEventTarget(event) {
34043
34288
  const target = event.target;
34044
34289
  if (target !== null && isHTMLElement(target) && target.shadowRoot !== null && typeof event.composedPath === "function") {
34045
- const path21 = event.composedPath();
34046
- if (path21.length > 0) {
34047
- return path21[0];
34290
+ const path22 = event.composedPath();
34291
+ if (path22.length > 0) {
34292
+ return path22[0];
34048
34293
  }
34049
34294
  }
34050
34295
  return target;
@@ -35955,7 +36200,7 @@ __export(Lexical_prod_exports, {
35955
36200
  $getNodeFromDOMNode: () => Qc,
35956
36201
  $getPreviousSelection: () => ds,
35957
36202
  $getRoot: () => ra,
35958
- $getSelection: () => fs8,
36203
+ $getSelection: () => fs9,
35959
36204
  $getSelectionSlotFrame: () => Df,
35960
36205
  $getSiblingCaret: () => ad,
35961
36206
  $getSlot: () => Lf,
@@ -36695,9 +36940,9 @@ function Ct(t7, e5, n7) {
36695
36940
  const o8 = performance.now() - _t > 100;
36696
36941
  try {
36697
36942
  al(t7, () => {
36698
- const r7 = fs8() || (function(t8) {
36943
+ const r7 = fs9() || (function(t8) {
36699
36944
  return t8.read("latest", () => {
36700
- const t9 = fs8();
36945
+ const t9 = fs9();
36701
36946
  return null !== t9 ? t9.clone() : null;
36702
36947
  });
36703
36948
  })(t7), i6 = /* @__PURE__ */ new Map(), s7 = t7._editorState, l8 = t7._blockCursorElement;
@@ -37977,7 +38222,7 @@ function Yo(t7, n7, o8) {
37977
38222
  al(n7, () => {
37978
38223
  if (!o8) return void sa(null);
37979
38224
  if (!Dc(n7, r7, s7)) return;
37980
- let a5 = fs8();
38225
+ let a5 = fs9();
37981
38226
  if (c10.postDeleteSelectionToRestore && zi(a5) && a5.isCollapsed()) {
37982
38227
  const t8 = a5.anchor, e5 = c10.postDeleteSelectionToRestore.anchor;
37983
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));
@@ -38019,7 +38264,7 @@ function Xo(t7, e5) {
38019
38264
  }
38020
38265
  function Qo(t7, e5) {
38021
38266
  al(e5, () => {
38022
- const n7 = fs8(), o8 = Qa(Wa(e5)), r7 = ds();
38267
+ const n7 = fs9(), o8 = Qa(Wa(e5)), r7 = ds();
38023
38268
  if (o8) {
38024
38269
  if (zi(n7)) {
38025
38270
  const t8 = n7.anchor, e6 = t8.getNode();
@@ -38068,7 +38313,7 @@ function er(t7) {
38068
38313
  if (null == t7 || t7.length <= 1 || null == e5) return;
38069
38314
  const n7 = 1 === e5.length ? e5 : "Enter" === e5 ? "\n" : "Tab" === e5 ? " " : null;
38070
38315
  if (!n7) return;
38071
- const o8 = fs8();
38316
+ const o8 = fs9();
38072
38317
  if (!zi(o8) || !o8.isCollapsed()) return;
38073
38318
  const r7 = o8.anchor.getNode();
38074
38319
  if (!ki(r7)) return;
@@ -38100,7 +38345,7 @@ function rr(t7, e5) {
38100
38345
  return null !== r7 && o8.contains(r7) && Ju(r7, e5);
38101
38346
  }
38102
38347
  function ir(t7) {
38103
- 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();
38104
38349
  if ("insertText" === n7 && t7.data && i6.isInsertTextAfterHandledSelectionCommand) {
38105
38350
  if (nr(i6), t7.preventDefault(), zi(s7) && !s7.isCollapsed()) {
38106
38351
  const t8 = s7.isBackward() ? s7.anchor : s7.focus;
@@ -38134,7 +38379,7 @@ function ir(t7) {
38134
38379
  let u9 = m && n8 && !a6 && c11;
38135
38380
  if (u9 && s7.isCollapsed() && (u9 = !Sl(Ea(s7.anchor, true))), !u9) {
38136
38381
  Ma(r7, jn, true);
38137
- const t8 = fs8();
38382
+ const t8 = fs9();
38138
38383
  m && zi(t8) && t8.isCollapsed() && (i6.postDeleteSelectionToRestore = t8, setTimeout(() => i6.postDeleteSelectionToRestore = null));
38139
38384
  }
38140
38385
  }
@@ -38234,7 +38479,7 @@ function sr(t7, e5) {
38234
38479
  }, { event: t7 }), n7.unprocessedBeforeInputData = null;
38235
38480
  }
38236
38481
  function lr(t7) {
38237
- 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);
38238
38483
  let s7 = false;
38239
38484
  if (null != r7 && zi(o8)) {
38240
38485
  const l8 = Qa(Wa(e5)), c10 = null !== l8 ? cu(l8, e5._rootElement) : null, a5 = "insertCompositionText" === t7.inputType && "ending-firefox" !== n7.compositionPhase && !e5.isComposing();
@@ -38259,13 +38504,13 @@ function cr(t7, e5) {
38259
38504
  Ma(e5, Wn, t7);
38260
38505
  }
38261
38506
  function ar(t7) {
38262
- const e5 = Vs(), n7 = e5._inputState, o8 = fs8();
38507
+ const e5 = Vs(), n7 = e5._inputState, o8 = fs9();
38263
38508
  if (zi(o8) && !e5.isComposing()) {
38264
38509
  n7.compositionPhase = "composing", n7.hadOrphanedCompositionEvents = false;
38265
38510
  const r7 = o8.anchor, i6 = o8.anchor.getNode();
38266
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())) {
38267
38512
  Ma(e5, Jn, F);
38268
- const t8 = fs8();
38513
+ const t8 = fs9();
38269
38514
  zi(t8) && Yc(t8.anchor.key);
38270
38515
  }
38271
38516
  }
@@ -38279,7 +38524,7 @@ function fr(t7) {
38279
38524
  if (null === t7) return;
38280
38525
  const e5 = Xc(t7);
38281
38526
  if (!ki(e5) || "text" === e5.getType() || Bc(e5) || !e5.isAttached()) return;
38282
- 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());
38283
38528
  if (r7.setFormat(e5.getFormat()), r7.setStyle(e5.getStyle()), e5.replace(r7), null !== o8) {
38284
38529
  const t8 = Math.min(o8, r7.getTextContentSize());
38285
38530
  r7.select(t8, t8);
@@ -38298,7 +38543,7 @@ function dr(t7, e5) {
38298
38543
  return fr(n7), false;
38299
38544
  }
38300
38545
  if ("\n" === e5[e5.length - 1]) {
38301
- const e6 = fs8();
38546
+ const e6 = fs9();
38302
38547
  if (zi(e6) || Bi(e6)) {
38303
38548
  if (zi(e6)) {
38304
38549
  const t8 = e6.focus;
@@ -38310,7 +38555,7 @@ function dr(t7, e5) {
38310
38555
  const o8 = Xc(n7);
38311
38556
  if (null !== o8 && ki(o8) && Bc(o8)) {
38312
38557
  o8.markDirty();
38313
- 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;
38314
38559
  return o8.select(i6, i6).insertText(e5), true;
38315
38560
  }
38316
38561
  }
@@ -38437,7 +38682,7 @@ function wr(t7, n7, o8) {
38437
38682
  const r7 = t7.__key, i6 = t7.getParent();
38438
38683
  if (null === i6) return void (null !== Of(t7) && e(367, r7, String(Of(t7))));
38439
38684
  const s7 = (function(t8) {
38440
- const e5 = fs8();
38685
+ const e5 = fs9();
38441
38686
  if (!zi(e5) || !yl(t8)) return e5;
38442
38687
  const { anchor: n8, focus: o9 } = e5, r8 = n8.getNode(), i7 = o9.getNode();
38443
38688
  return Ba(r8, t8) && n8.set(t8.__key, 0, "element"), Ba(i7, t8) && o9.set(t8.__key, 0, "element"), e5;
@@ -38509,7 +38754,7 @@ var Fr = class {
38509
38754
  return false;
38510
38755
  }
38511
38756
  isSelected(t7) {
38512
- const e5 = t7 || fs8();
38757
+ const e5 = t7 || fs9();
38513
38758
  if (null == e5) return false;
38514
38759
  const n7 = e5.getNodes().some((t8) => t8.__key === this.__key);
38515
38760
  if (ki(this)) return n7;
@@ -38699,7 +38944,7 @@ var Fr = class {
38699
38944
  }
38700
38945
  replace(t7, n7) {
38701
38946
  Ws();
38702
- let o8 = fs8();
38947
+ let o8 = fs9();
38703
38948
  null !== o8 && (o8 = o8.clone()), qa(this, t7);
38704
38949
  const r7 = this.getLatest(), i6 = this.__key, s7 = Mf(r7);
38705
38950
  null !== s7 && e(400, i6, r7.getType(), s7.getKey(), s7.getType());
@@ -38719,7 +38964,7 @@ var Fr = class {
38719
38964
  Ws(), qa(this, t7);
38720
38965
  const n7 = this.getWritable(), o8 = t7.getWritable();
38721
38966
  this.getParentOrThrow();
38722
- const r7 = o8.getParent(), i6 = fs8();
38967
+ const r7 = o8.getParent(), i6 = fs9();
38723
38968
  let s7 = false, l8 = false, c10 = -1;
38724
38969
  if (null !== r7 && e5 && zi(i6) && hs(i6, r7)) {
38725
38970
  const e6 = r7.__key, n8 = i6.anchor, o9 = i6.focus;
@@ -38740,7 +38985,7 @@ var Fr = class {
38740
38985
  Ws(), qa(this, t7);
38741
38986
  const n7 = this.getWritable(), o8 = t7.getWritable();
38742
38987
  this.getParentOrThrow();
38743
- 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;
38744
38989
  Jc(o8), l8 && null !== s7 && zi(i6) && gs(i6, s7, c10, -1);
38745
38990
  const a5 = this.getPreviousSibling(), u8 = this.getParentOrThrow().getWritable(), f6 = n7.__prev, d9 = e5 && zi(i6) && hs(i6, u8), h9 = d9 ? this.getIndexWithinParent() : -1;
38746
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;
@@ -39138,7 +39383,7 @@ var hi = class extends Fr {
39138
39383
  select(t7, e5) {
39139
39384
  Ws();
39140
39385
  let n7 = t7, o8 = e5;
39141
- const r7 = fs8(), i6 = this.getTextContent(), s7 = this.__key;
39386
+ const r7 = fs9(), i6 = this.getTextContent(), s7 = this.__key;
39142
39387
  if ("string" == typeof i6) {
39143
39388
  const t8 = i6.length;
39144
39389
  void 0 === n7 && (n7 = t8), void 0 === o8 && (o8 = t8);
@@ -39161,7 +39406,7 @@ var hi = class extends Fr {
39161
39406
  const r7 = this.getWritable(), i6 = r7.__text, s7 = n7.length;
39162
39407
  let l8 = t7;
39163
39408
  l8 < 0 && (l8 = s7 + l8, l8 < 0 && (l8 = 0));
39164
- const c10 = fs8();
39409
+ const c10 = fs9();
39165
39410
  if (o8 && zi(c10)) {
39166
39411
  const e6 = t7 + s7;
39167
39412
  c10.setTextNodeRange(r7, e6, r7, e6);
@@ -39192,7 +39437,7 @@ var hi = class extends Fr {
39192
39437
  let f6;
39193
39438
  const d9 = e5.getFormat(), h9 = e5.getStyle(), g8 = e5.__detail;
39194
39439
  let _5 = false, p7 = null, y5 = null;
39195
- const m11 = fs8();
39440
+ const m11 = fs9();
39196
39441
  if (zi(m11)) {
39197
39442
  const [t8, e6] = m11.isBackward() ? [m11.focus, m11.anchor] : [m11.anchor, m11.focus];
39198
39443
  "text" === t8.type && t8.key === o8 && (p7 = t8), "text" === e6.type && e6.key === o8 && (y5 = e6);
@@ -39231,7 +39476,7 @@ var hi = class extends Fr {
39231
39476
  n7 || t7 === this.getNextSibling() || e(50);
39232
39477
  const o8 = this.__key, r7 = t7.__key, i6 = this.__text, s7 = i6.length;
39233
39478
  Gc() === r7 && Yc(o8);
39234
- const l8 = fs8();
39479
+ const l8 = fs9();
39235
39480
  if (zi(l8)) {
39236
39481
  const e5 = l8.anchor, i7 = l8.focus;
39237
39482
  null !== e5 && e5.key === r7 && ys(e5, n7, o8, t7, s7), null !== i7 && i7.key === r7 && ys(i7, n7, o8, t7, s7);
@@ -39518,7 +39763,7 @@ var Ii = class _Ii {
39518
39763
  }
39519
39764
  deleteNodes() {
39520
39765
  const t7 = this.getNodes().filter((t8) => null === Of(t8));
39521
- if ((fs8() || ds()) === this && t7[0]) {
39766
+ if ((fs9() || ds()) === this && t7[0]) {
39522
39767
  const e5 = ad(t7[0], "next");
39523
39768
  Kl(vd(e5, e5));
39524
39769
  }
@@ -39528,7 +39773,7 @@ var Ii = class _Ii {
39528
39773
  };
39529
39774
  function Li() {
39530
39775
  const t7 = ra();
39531
- if (0 === t7.getChildrenSize() && null === Df(fs8())) {
39776
+ if (0 === t7.getChildrenSize() && null === Df(fs9())) {
39532
39777
  const e5 = oc();
39533
39778
  t7.append(e5), e5.select();
39534
39779
  }
@@ -39655,8 +39900,8 @@ var Ki = class _Ki {
39655
39900
  else {
39656
39901
  const t8 = vi(r7.getTextContent());
39657
39902
  t8.setFormat(n7), t8.setStyle(o8);
39658
- const e6 = fs8() === this;
39659
- 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);
39660
39905
  }
39661
39906
  return void ("" !== t7 && this.insertText(t7));
39662
39907
  }
@@ -39691,14 +39936,14 @@ var Ki = class _Ki {
39691
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);
39692
39937
  }
39693
39938
  removeText() {
39694
- const t7 = fs8() === this, e5 = this.anchor.key;
39939
+ const t7 = fs9() === this, e5 = this.anchor.key;
39695
39940
  Bl(this, Ul(Rl(this))), this.isCollapsed() && (function(t8, e6) {
39696
39941
  const n7 = t8.anchor;
39697
39942
  if (n7.key === e6) return;
39698
39943
  const o8 = n7.getNode();
39699
39944
  let r7 = 0, i6 = "";
39700
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);
39701
- })(this, e5), t7 && fs8() !== this && sa(this);
39946
+ })(this, e5), t7 && fs9() !== this && sa(this);
39702
39947
  }
39703
39948
  formatText(t7, e5 = null) {
39704
39949
  $i(this, t7, e5);
@@ -39715,7 +39960,7 @@ var Ki = class _Ki {
39715
39960
  }
39716
39961
  if (null !== o9) {
39717
39962
  o9.selectStart();
39718
- const n8 = fs8();
39963
+ const n8 = fs9();
39719
39964
  return zi(n8) || e(369), n8.insertNodes(t7);
39720
39965
  }
39721
39966
  }
@@ -40309,7 +40554,7 @@ function us(t7, e5, n7, o8) {
40309
40554
  const x9 = new Ki(g8, _5, y5, m11);
40310
40555
  return p7 && (x9.dirty = true), x9;
40311
40556
  }
40312
- function fs8() {
40557
+ function fs9() {
40313
40558
  return js()._selection;
40314
40559
  }
40315
40560
  function ds() {
@@ -40479,7 +40724,7 @@ function Ss(t7, e5, n7, o8, r7, i6) {
40479
40724
  }
40480
40725
  }
40481
40726
  function Cs(t7) {
40482
- let e5 = fs8() || ds();
40727
+ let e5 = fs9() || ds();
40483
40728
  null === e5 && (e5 = ra().selectEnd()), e5.insertNodes(t7);
40484
40729
  }
40485
40730
  function vs(t7, e5) {
@@ -40490,7 +40735,7 @@ function ks(t7) {
40490
40735
  return vs(t7, { linebreak: () => e5.push(Al()), tab: () => e5.push(wi()), text: (t8) => e5.push(vi(t8)) }), e5;
40491
40736
  }
40492
40737
  function bs() {
40493
- const t7 = fs8();
40738
+ const t7 = fs9();
40494
40739
  return null === t7 ? "" : t7.getTextContent();
40495
40740
  }
40496
40741
  function Ts(t7) {
@@ -40501,7 +40746,7 @@ function Ts(t7) {
40501
40746
  function Ns(t7, n7 = false) {
40502
40747
  let o8 = t7;
40503
40748
  t7.isCollapsed() || o8.removeText();
40504
- const r7 = fs8();
40749
+ const r7 = fs9();
40505
40750
  zi(r7) && (o8 = r7), zi(o8) || e(161);
40506
40751
  const i6 = o8.anchor;
40507
40752
  let s7 = i6.getNode(), l8 = i6.offset;
@@ -41184,7 +41429,7 @@ var pl = class extends Fr {
41184
41429
  }
41185
41430
  select(t7, e5) {
41186
41431
  Ws();
41187
- const n7 = fs8();
41432
+ const n7 = fs9();
41188
41433
  let o8 = t7, r7 = e5;
41189
41434
  const i6 = this.getChildrenSize();
41190
41435
  if (!this.canBeEmpty()) {
@@ -41281,7 +41526,7 @@ var pl = class extends Fr {
41281
41526
  } else t8.__prev = null;
41282
41527
  }
41283
41528
  if (i6.__size = f6, c10.length) {
41284
- const t8 = fs8();
41529
+ const t8 = fs9();
41285
41530
  if (zi(t8)) {
41286
41531
  const e5 = new Set(c10), n8 = new Set(l8), { anchor: o9, focus: r8 } = t8;
41287
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();
@@ -41564,7 +41809,7 @@ function zl(t7, n7) {
41564
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"));
41565
41810
  }
41566
41811
  function Kl(t7) {
41567
- const e5 = fs8(), n7 = zi(e5) ? e5 : ls();
41812
+ const e5 = fs9(), n7 = zi(e5) ? e5 : ls();
41568
41813
  return Bl(n7, t7), sa(n7), n7;
41569
41814
  }
41570
41815
  function Bl(t7, e5) {
@@ -42184,7 +42429,7 @@ var vc = class {
42184
42429
  focus(t7, e5 = {}) {
42185
42430
  const n7 = this._rootElement;
42186
42431
  null !== n7 && (n7.setAttribute("autocapitalize", "off"), al(this, () => {
42187
- const o8 = fs8(), r7 = ra();
42432
+ const o8 = fs9(), r7 = ra();
42188
42433
  null !== o8 ? o8.dirty || sa(o8.clone()) : 0 !== r7.getChildrenSize() && ("rootStart" === e5.defaultSelection ? r7.selectStart() : r7.selectEnd()), za("focus"), Ka(() => {
42189
42434
  n7.removeAttribute("autocapitalize"), t7 && t7();
42190
42435
  });
@@ -42446,7 +42691,7 @@ function ga(t7, e5, n7, o8, r7) {
42446
42691
  }
42447
42692
  const c10 = i6.getTextContent();
42448
42693
  if (r7 || l8 !== c10) {
42449
- const e6 = fs8();
42694
+ const e6 = fs9();
42450
42695
  if ("" === l8) {
42451
42696
  if (Yc(null), p || g || x) i6.remove();
42452
42697
  else {
@@ -44140,10 +44385,10 @@ function _loadWasmModule(sync, filepath, src, imports2) {
44140
44385
  var buf = null;
44141
44386
  var isNode2 = typeof process !== "undefined" && process.versions != null && process.versions.node != null;
44142
44387
  if (filepath && isNode2) {
44143
- var fs27 = null;
44144
- var path21 = null;
44388
+ var fs28 = null;
44389
+ var path22 = null;
44145
44390
  return new Promise((resolve, reject) => {
44146
- fs27.readFile(path21.resolve(__dirname, filepath), (error, buffer) => {
44391
+ fs28.readFile(path22.resolve(__dirname, filepath), (error, buffer) => {
44147
44392
  if (error != null) {
44148
44393
  reject(error);
44149
44394
  } else {
@@ -45446,10 +45691,10 @@ var LoroDoc = class _LoroDoc {
45446
45691
  * @param {string} path
45447
45692
  * @returns {Value | Container | undefined}
45448
45693
  */
45449
- getByPath(path21) {
45694
+ getByPath(path22) {
45450
45695
  try {
45451
45696
  const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
45452
- const ptr0 = passStringToWasm0(path21, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
45697
+ const ptr0 = passStringToWasm0(path22, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
45453
45698
  const len0 = WASM_VECTOR_LEN;
45454
45699
  wasm.lorodoc_getByPath(retptr, this.__wbg_ptr, ptr0, len0);
45455
45700
  var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true);
@@ -87424,7 +87669,7 @@ async function readText(file) {
87424
87669
  }
87425
87670
  }
87426
87671
  async function writeAtomic(file, data) {
87427
- 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 });
87428
87673
  const tmp = `${file}.${process.pid}.tmp`;
87429
87674
  await import_promises.default.writeFile(tmp, data);
87430
87675
  await import_promises.default.rename(tmp, file);
@@ -87633,13 +87878,13 @@ function outcomeWarnings(outcome, streaming) {
87633
87878
  function createDocumentRegistry(options) {
87634
87879
  const sessions = /* @__PURE__ */ new Map();
87635
87880
  const warnings = /* @__PURE__ */ new Map();
87636
- 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");
87637
87882
  const commentStores = /* @__PURE__ */ new Map();
87638
87883
  const cursors = createCommentCursors(options.stateDir);
87639
87884
  const commentStore = (id2) => {
87640
87885
  let store = commentStores.get(id2);
87641
87886
  if (!store) {
87642
- 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);
87643
87888
  commentStores.set(id2, store);
87644
87889
  }
87645
87890
  return store;
@@ -87653,10 +87898,10 @@ function createDocumentRegistry(options) {
87653
87898
  if (JSON.stringify(store.warnings()) !== before2) options.onWarningsChange(id2);
87654
87899
  };
87655
87900
  const create3 = async (id2) => {
87656
- if (!import_node_fs8.default.existsSync(docFile(id2))) return null;
87901
+ if (!import_node_fs9.default.existsSync(docFile(id2))) return null;
87657
87902
  const session = await createDocumentSession({
87658
87903
  file: docFile(id2),
87659
- 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"),
87660
87905
  ...options.timing,
87661
87906
  log: options.log
87662
87907
  });
@@ -87699,7 +87944,7 @@ function createDocumentRegistry(options) {
87699
87944
  const known = sessions.has(id2);
87700
87945
  void open(id2).then((session) => {
87701
87946
  if (!session || !known) return;
87702
- import_node_fs8.default.readFile(docFile(id2), "utf8", (err, text2) => {
87947
+ import_node_fs9.default.readFile(docFile(id2), "utf8", (err, text2) => {
87703
87948
  if (!err) session.onFileText(text2);
87704
87949
  });
87705
87950
  });
@@ -88263,9 +88508,9 @@ function injectRuntime(html, options = {}) {
88263
88508
  }
88264
88509
 
88265
88510
  // src/server/http.ts
88266
- var import_node_crypto2 = __toESM(require("node:crypto"), 1);
88267
- var import_node_fs9 = __toESM(require("node:fs"), 1);
88268
- 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);
88269
88514
  var MIME2 = {
88270
88515
  ".html": "text/html; charset=utf-8",
88271
88516
  ".js": "text/javascript; charset=utf-8",
@@ -88288,12 +88533,12 @@ var MIME2 = {
88288
88533
  ".mp4": "video/mp4",
88289
88534
  ".webm": "video/webm"
88290
88535
  };
88291
- var mimeOf = (file) => MIME2[import_node_path9.default.extname(file).toLowerCase()] ?? "application/octet-stream";
88292
- 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");
88293
88538
  function safeEqual(a5, b6) {
88294
88539
  const ab = Buffer.from(a5);
88295
88540
  const bb = Buffer.from(b6);
88296
- 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);
88297
88542
  }
88298
88543
  function parseCookies(header) {
88299
88544
  const cookies = {};
@@ -88305,7 +88550,7 @@ function parseCookies(header) {
88305
88550
  }
88306
88551
  function readPersisted(file) {
88307
88552
  try {
88308
- return import_node_fs9.default.readFileSync(file, "utf8").trim();
88553
+ return import_node_fs10.default.readFileSync(file, "utf8").trim();
88309
88554
  } catch {
88310
88555
  return "";
88311
88556
  }
@@ -88334,15 +88579,15 @@ function sendJson(res, status, value) {
88334
88579
  send(res, status, JSON.stringify(value), { "Content-Type": MIME2[".json"] });
88335
88580
  }
88336
88581
  function sendFile(res, file, headers = {}) {
88337
- import_node_fs9.default.readFile(file, (err, data) => {
88582
+ import_node_fs10.default.readFile(file, (err, data) => {
88338
88583
  if (err) send(res, 404, "Not found");
88339
88584
  else send(res, 200, data, { "Content-Type": mimeOf(file), ...headers });
88340
88585
  });
88341
88586
  }
88342
88587
 
88343
88588
  // src/server/dynamic-blocks.ts
88344
- var import_node_fs10 = __toESM(require("node:fs"), 1);
88345
- 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);
88346
88591
  function dynamicUrls(xml) {
88347
88592
  const urls = /* @__PURE__ */ new Set();
88348
88593
  for (const match of stripOpaque(xml).matchAll(TAG)) {
@@ -88355,7 +88600,7 @@ function dynamicUrls(xml) {
88355
88600
  }
88356
88601
  var mtimeOf = (file) => {
88357
88602
  try {
88358
- return import_node_fs10.default.statSync(file).mtimeMs;
88603
+ return import_node_fs11.default.statSync(file).mtimeMs;
88359
88604
  } catch {
88360
88605
  return -1;
88361
88606
  }
@@ -88372,7 +88617,7 @@ function createDynamicBlocks(limits) {
88372
88617
  };
88373
88618
  const entry = (id2, dir, file) => {
88374
88619
  const map = files(id2);
88375
- const mtime = mtimeOf(import_node_path10.default.join(dir, file));
88620
+ const mtime = mtimeOf(import_node_path11.default.join(dir, file));
88376
88621
  let state = map.get(file);
88377
88622
  if (!state || state.mtime !== mtime) {
88378
88623
  state = { mtime, errors: [], ran: false };
@@ -88397,19 +88642,19 @@ function createDynamicBlocks(limits) {
88397
88642
  state(id2, dir) {
88398
88643
  let xml = "";
88399
88644
  try {
88400
- 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");
88401
88646
  } catch {
88402
88647
  return { warnings: [], dynamic: {} };
88403
88648
  }
88404
88649
  const urls = dynamicUrls(xml);
88405
88650
  const map = files(id2);
88406
88651
  for (const [file, state] of map) {
88407
- 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);
88408
88653
  }
88409
88654
  const warnings = [];
88410
88655
  const dynamic = {};
88411
88656
  for (const url of urls) {
88412
- 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`);
88413
88658
  const state = map.get(url);
88414
88659
  for (const error of state?.errors ?? []) warnings.push(`runtime: ${url}: ${error}`);
88415
88660
  dynamic[url] = { ran: state?.ran ?? false };
@@ -88420,8 +88665,8 @@ function createDynamicBlocks(limits) {
88420
88665
  }
88421
88666
 
88422
88667
  // src/server/selection.ts
88423
- var import_node_fs11 = __toESM(require("node:fs"), 1);
88424
- 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);
88425
88670
 
88426
88671
  // src/shared/selection.ts
88427
88672
  var SELECTION_FULL_NODES = 20;
@@ -88496,14 +88741,14 @@ function readAnySelection(value) {
88496
88741
  function createSelectionStore(stateDir, exists) {
88497
88742
  const tabs = /* @__PURE__ */ new Map();
88498
88743
  let clock = 0;
88499
- 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");
88500
88745
  const write = (kind, id2) => {
88501
88746
  let winner;
88502
88747
  for (const tab of tabs.values()) {
88503
88748
  if (tab.kind !== kind || tab.id !== id2) continue;
88504
88749
  if (!winner || tab.humanAt > winner.humanAt || tab.humanAt === winner.humanAt && tab.seenAt > winner.seenAt) winner = tab;
88505
88750
  }
88506
- 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 });
88507
88752
  else writeJsonAtomic(fileOf(kind, id2), { artifact: `${kind}/${id2}`, updatedAt: (/* @__PURE__ */ new Date()).toISOString(), ...winner.state });
88508
88753
  };
88509
88754
  return {
@@ -88534,19 +88779,19 @@ function createSelectionStore(stateDir, exists) {
88534
88779
  for (const kind of ["artboard", "document"]) {
88535
88780
  let ids = [];
88536
88781
  try {
88537
- 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));
88538
88783
  } catch {
88539
88784
  continue;
88540
88785
  }
88541
- 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 });
88542
88787
  }
88543
88788
  }
88544
88789
  };
88545
88790
  }
88546
88791
 
88547
88792
  // src/server/config.ts
88548
- var import_node_fs12 = __toESM(require("node:fs"), 1);
88549
- 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);
88550
88795
 
88551
88796
  // src/shared/cloud.ts
88552
88797
  var ownLink = (url, hostId) => hostId ? `${url}#ryugi-host=${hostId}` : url;
@@ -88581,7 +88826,7 @@ function normalize(raw) {
88581
88826
  function readConfig(file) {
88582
88827
  let text2;
88583
88828
  try {
88584
- text2 = import_node_fs12.default.readFileSync(file, "utf8");
88829
+ text2 = import_node_fs13.default.readFileSync(file, "utf8");
88585
88830
  } catch {
88586
88831
  return { config: defaults() };
88587
88832
  }
@@ -88592,22 +88837,22 @@ function readConfig(file) {
88592
88837
  }
88593
88838
  }
88594
88839
  function ensureGitignore(dir) {
88595
- const file = import_node_path12.default.join(dir, ".gitignore");
88840
+ const file = import_node_path13.default.join(dir, ".gitignore");
88596
88841
  let current = "";
88597
88842
  try {
88598
- current = import_node_fs12.default.readFileSync(file, "utf8");
88843
+ current = import_node_fs13.default.readFileSync(file, "utf8");
88599
88844
  } catch {
88600
- import_node_fs12.default.writeFileSync(file, "config.json\n");
88845
+ import_node_fs13.default.writeFileSync(file, "config.json\n");
88601
88846
  return;
88602
88847
  }
88603
88848
  if (current.split(/\r?\n/).some((line) => line.trim() === "config.json")) return;
88604
- 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
88605
88850
  `);
88606
88851
  }
88607
88852
  function ensureConfig(file) {
88608
- import_node_fs12.default.mkdirSync(import_node_path12.default.dirname(file), { recursive: true });
88609
- ensureGitignore(import_node_path12.default.dirname(file));
88610
- 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());
88611
88856
  }
88612
88857
  function updateArtifactConfig(file, key2, patch) {
88613
88858
  const { config, warning } = readConfig(file);
@@ -88627,21 +88872,21 @@ function setProjectAutoPublish(file, value) {
88627
88872
  if (warning) throw new Error(warning);
88628
88873
  if (value === null) delete config.autoPublish;
88629
88874
  else config.autoPublish = value;
88630
- 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 });
88631
88876
  writeJsonAtomic(file, config);
88632
88877
  }
88633
88878
 
88634
88879
  // src/server/credentials.ts
88635
- var import_node_fs13 = __toESM(require("node:fs"), 1);
88636
- 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);
88637
88882
  function credentialsFile(env, platform, home) {
88638
- if (platform === "win32" && env.APPDATA) return import_node_path13.default.join(env.APPDATA, "ryugi", "credentials.json");
88639
- 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");
88640
88885
  }
88641
88886
  function readCredentials(file, origin) {
88642
88887
  let raw;
88643
88888
  try {
88644
- raw = JSON.parse(import_node_fs13.default.readFileSync(file, "utf8"));
88889
+ raw = JSON.parse(import_node_fs14.default.readFileSync(file, "utf8"));
88645
88890
  } catch {
88646
88891
  return null;
88647
88892
  }
@@ -88652,11 +88897,11 @@ function readCredentials(file, origin) {
88652
88897
  return { origin: savedOrigin, token, login };
88653
88898
  }
88654
88899
  function writeCredentials(file, credentials) {
88655
- 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 });
88656
88901
  writeJsonAtomic(file, credentials);
88657
88902
  }
88658
88903
  function clearCredentials(file) {
88659
- import_node_fs13.default.rmSync(file, { force: true });
88904
+ import_node_fs14.default.rmSync(file, { force: true });
88660
88905
  }
88661
88906
 
88662
88907
  // node_modules/.pnpm/ws@8.18.3/node_modules/ws/wrapper.mjs
@@ -88784,7 +89029,7 @@ function createRelayClient(options) {
88784
89029
  }
88785
89030
 
88786
89031
  // src/server/ws.ts
88787
- var import_node_crypto3 = __toESM(require("node:crypto"), 1);
89032
+ var import_node_crypto4 = __toESM(require("node:crypto"), 1);
88788
89033
  var WS_MAGIC = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
88789
89034
  var OPCODE_TEXT = 1;
88790
89035
  var OPCODE_CLOSE = 8;
@@ -88792,7 +89037,7 @@ var OPCODE_PING = 9;
88792
89037
  var OPCODE_PONG = 10;
88793
89038
  var MAX_CLIENT_FRAME = 8 * 1024 * 1024;
88794
89039
  function acceptKey(clientKey) {
88795
- 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");
88796
89041
  }
88797
89042
  function encodeFrame(opcode, payload) {
88798
89043
  const len = payload.length;
@@ -88917,21 +89162,21 @@ function createDirectRelay(options) {
88917
89162
  }
88918
89163
 
88919
89164
  // src/server/host-id.ts
88920
- var import_node_fs14 = __toESM(require("node:fs"), 1);
88921
- 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);
88922
89167
  function hostIdFor(credentialsFile2) {
88923
- 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");
88924
89169
  const saved = readPersisted(file);
88925
89170
  if (/^[A-Za-z0-9_-]{16,64}$/.test(saved)) return saved;
88926
89171
  const id2 = randomToken();
88927
- import_node_fs14.default.mkdirSync(import_node_path14.default.dirname(file), { recursive: true, mode: 448 });
88928
- 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}
88929
89174
  `, { mode: 384 });
88930
89175
  return id2;
88931
89176
  }
88932
89177
 
88933
89178
  // src/server/avatars.ts
88934
- var import_node_crypto4 = require("node:crypto");
89179
+ var import_node_crypto5 = require("node:crypto");
88935
89180
  var AVATAR_ROUTE = /^\/api\/avatar\/([a-z]+)\/([^/]+)$/;
88936
89181
  var MAX_BYTES = 512 * 1024;
88937
89182
  var TIMEOUT_MS = 5e3;
@@ -88940,7 +89185,7 @@ var SOURCES = {
88940
89185
  gravatar: { valid: /^[0-9a-f]{64}$/, url: (id2) => `https://gravatar.com/avatar/${id2}?s=64&d=404` }
88941
89186
  };
88942
89187
  var githubAvatar = (login) => `/api/avatar/github/${login}`;
88943
- 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")}`;
88944
89189
  function createAvatars(fetchImpl = fetch) {
88945
89190
  const cache = /* @__PURE__ */ new Map();
88946
89191
  const fetchAvatar = async (url) => {
@@ -88972,7 +89217,7 @@ function createAvatars(fetchImpl = fetch) {
88972
89217
  }
88973
89218
 
88974
89219
  // src/server/cloud.ts
88975
- var import_node_crypto5 = __toESM(require("node:crypto"), 1);
89220
+ var import_node_crypto6 = __toESM(require("node:crypto"), 1);
88976
89221
 
88977
89222
  // src/shared/brand.ts
88978
89223
  var RYUGI_MARK_VIEWBOX = "0 0 512 512";
@@ -89082,32 +89327,32 @@ function callbackPage(result) {
89082
89327
  }
89083
89328
 
89084
89329
  // src/server/pack.ts
89085
- var import_node_fs15 = __toESM(require("node:fs"), 1);
89086
- 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);
89087
89332
  var MAX_ARTIFACT_BYTES = 20 * 1024 * 1024;
89088
89333
  function formatBytes(bytes) {
89089
89334
  if (bytes < 1024) return `${bytes} B`;
89090
89335
  if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
89091
89336
  return `${(bytes / 1024 / 1024).toFixed(1)} MB`;
89092
89337
  }
89093
- var excluded = (type, relative) => type === "document" && relative === "comments.json";
89338
+ var excluded = (type, relative) => (type === "document" || type === "artboard") && relative === "comments.json";
89094
89339
  function packArtifact(dir, type, { maxBytes = MAX_ARTIFACT_BYTES } = {}) {
89095
- const rootReal = import_node_fs15.default.realpathSync(dir);
89340
+ const rootReal = import_node_fs16.default.realpathSync(dir);
89096
89341
  const found = [];
89097
89342
  const visited = /* @__PURE__ */ new Set([rootReal]);
89098
89343
  let total = 0;
89099
89344
  const walk = (current) => {
89100
- 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 })) {
89101
89346
  if (entry.name.startsWith(".")) continue;
89102
- const full = import_node_path15.default.join(current, entry.name);
89347
+ const full = import_node_path16.default.join(current, entry.name);
89103
89348
  let real;
89104
89349
  try {
89105
- real = import_node_fs15.default.realpathSync(full);
89350
+ real = import_node_fs16.default.realpathSync(full);
89106
89351
  } catch {
89107
89352
  continue;
89108
89353
  }
89109
- if (!real.startsWith(rootReal + import_node_path15.default.sep)) continue;
89110
- 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);
89111
89356
  if (stat.isDirectory()) {
89112
89357
  if (visited.has(real)) continue;
89113
89358
  visited.add(real);
@@ -89115,7 +89360,7 @@ function packArtifact(dir, type, { maxBytes = MAX_ARTIFACT_BYTES } = {}) {
89115
89360
  continue;
89116
89361
  }
89117
89362
  if (!stat.isFile()) continue;
89118
- 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("/");
89119
89364
  if (excluded(type, relative)) continue;
89120
89365
  total += stat.size;
89121
89366
  found.push({ path: relative, real });
@@ -89125,12 +89370,12 @@ function packArtifact(dir, type, { maxBytes = MAX_ARTIFACT_BYTES } = {}) {
89125
89370
  if (total > maxBytes) {
89126
89371
  throw new Error(`artifact is ${formatBytes(total)}; the limit is ${formatBytes(maxBytes)}`);
89127
89372
  }
89128
- 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) }));
89129
89374
  }
89130
89375
 
89131
89376
  // src/server/publish-payload.ts
89132
- var import_node_fs16 = __toESM(require("node:fs"), 1);
89133
- 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);
89134
89379
  var CLOUD_RUNTIME_URL = "/_ryugi/runtime/ryugi-ui.js";
89135
89380
  function sharedComments(file) {
89136
89381
  return {
@@ -89143,7 +89388,7 @@ function publishPayload(ref, dir, title, payload, comments = emptyComments()) {
89143
89388
  const { canvas, layout, versions } = payload;
89144
89389
  return { canvas, layout, versions };
89145
89390
  }
89146
- 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) };
89147
89392
  return { title };
89148
89393
  }
89149
89394
  var isPage = (type, file) => type === "html" ? file === "index.html" : (type === "document" || type === "artboard") && file.endsWith(".html");
@@ -89154,12 +89399,12 @@ function withCloudRuntime(type, files) {
89154
89399
  }
89155
89400
 
89156
89401
  // src/server/settings.ts
89157
- var import_node_fs17 = __toESM(require("node:fs"), 1);
89158
- var import_node_path17 = __toESM(require("node:path"), 1);
89159
- 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");
89160
89405
  function readSettings(file) {
89161
89406
  try {
89162
- const raw = JSON.parse(import_node_fs17.default.readFileSync(file, "utf8"));
89407
+ const raw = JSON.parse(import_node_fs18.default.readFileSync(file, "utf8"));
89163
89408
  return typeof raw === "object" && raw !== null && !Array.isArray(raw) ? raw : {};
89164
89409
  } catch {
89165
89410
  return {};
@@ -89170,7 +89415,7 @@ function readGlobalAutoPublish(file) {
89170
89415
  return typeof value === "boolean" ? value : void 0;
89171
89416
  }
89172
89417
  function writeGlobalAutoPublish(file, value) {
89173
- 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 });
89174
89419
  writeJsonAtomic(file, { ...readSettings(file), autoPublish: value });
89175
89420
  }
89176
89421
  function defaultGlobalAutoPublish(file) {
@@ -89242,10 +89487,10 @@ function createCloudRoutes(options) {
89242
89487
  const state = randomToken();
89243
89488
  const verifier = randomToken();
89244
89489
  pendingStates.set(state, { expires: now + LOGIN_STATE_TTL_MS, verifier });
89245
- 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");
89246
89491
  return `${origin}/auth/cli?port=${options.port()}&state=${state}&challenge=${challenge}`;
89247
89492
  };
89248
- const cloudFetch = async (method, path21, body) => {
89493
+ const cloudFetch = async (method, path22, body) => {
89249
89494
  const credentials = readCredentials(credentialsFile2, origin);
89250
89495
  if (!credentials) throw new SignedOut();
89251
89496
  const headers = { Authorization: `Bearer ${credentials.token}` };
@@ -89259,7 +89504,7 @@ function createCloudRoutes(options) {
89259
89504
  }
89260
89505
  let res;
89261
89506
  try {
89262
- res = await fetch(`${origin}${path21}`, { method, headers, body: payload });
89507
+ res = await fetch(`${origin}${path22}`, { method, headers, body: payload });
89263
89508
  } catch (err) {
89264
89509
  throw new CloudRefused(502, `could not reach ${origin}: ${err.message}`);
89265
89510
  }
@@ -89316,9 +89561,9 @@ function createCloudRoutes(options) {
89316
89561
  }
89317
89562
  const key2 = `${ref.type}/${ref.id}`;
89318
89563
  const entry = artifactEntry(key2);
89319
- 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") }));
89320
89565
  const byHash = new Map(hashed.map((file) => [file.hash, file.data]));
89321
- const manifest = hashed.map(({ path: path21, hash }) => ({ path: path21, hash }));
89566
+ const manifest = hashed.map(({ path: path22, hash }) => ({ path: path22, hash }));
89322
89567
  const version2 = (visibility) => ({ title: inspected.title, payload: inspected.payload, files: manifest, project, ...visibility ? { visibility } : {} });
89323
89568
  const create3 = () => createRecord2(ref, requested ?? entry.visibility ?? "private");
89324
89569
  const upload = async (id2, hashes) => {
@@ -89541,7 +89786,7 @@ function createCloudRoutes(options) {
89541
89786
 
89542
89787
  // src/server/comment-names.ts
89543
89788
  var import_node_child_process = require("node:child_process");
89544
- var import_node_fs18 = __toESM(require("node:fs"), 1);
89789
+ var import_node_fs19 = __toESM(require("node:fs"), 1);
89545
89790
 
89546
89791
  // src/server/agent.ts
89547
89792
  var NAMES = {
@@ -89622,7 +89867,7 @@ function commentNames({ agentFile, projectDir, cloud }) {
89622
89867
  },
89623
89868
  agent: () => {
89624
89869
  try {
89625
- return detectAgent(JSON.parse(import_node_fs18.default.readFileSync(agentFile, "utf8")));
89870
+ return detectAgent(JSON.parse(import_node_fs19.default.readFileSync(agentFile, "utf8")));
89626
89871
  } catch {
89627
89872
  return void 0;
89628
89873
  }
@@ -89650,8 +89895,8 @@ function viewerPage(template, page) {
89650
89895
  }
89651
89896
 
89652
89897
  // src/server/auto-publish.ts
89653
- var import_node_crypto6 = __toESM(require("node:crypto"), 1);
89654
- 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);
89655
89900
  var SIGNED_OUT_MESSAGE = "auto-publish paused: the Ryugi Cloud sign-in expired";
89656
89901
  function isNotice(value) {
89657
89902
  if (typeof value !== "object" || value === null) return false;
@@ -89661,7 +89906,7 @@ function isNotice(value) {
89661
89906
  function readAutoPublishState(file) {
89662
89907
  let raw = {};
89663
89908
  try {
89664
- const parsed = JSON.parse(import_node_fs19.default.readFileSync(file, "utf8"));
89909
+ const parsed = JSON.parse(import_node_fs20.default.readFileSync(file, "utf8"));
89665
89910
  if (typeof parsed === "object" && parsed !== null) raw = parsed;
89666
89911
  } catch {
89667
89912
  }
@@ -89681,7 +89926,7 @@ function createAutoPublisher(options) {
89681
89926
  const remarked = /* @__PURE__ */ new Set();
89682
89927
  const save = () => writeJsonAtomic(options.stateFile, { dirty: [...dirty], notice, paused });
89683
89928
  const raise = (kind, message) => {
89684
- 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 };
89685
89930
  };
89686
89931
  const run2 = async () => {
89687
89932
  if (!options.enabled() || dirty.size === 0) return;
@@ -89741,7 +89986,7 @@ function createAutoPublisher(options) {
89741
89986
  }
89742
89987
 
89743
89988
  // src/server/artifact-file.ts
89744
- var import_node_fs20 = __toESM(require("node:fs"), 1);
89989
+ var import_node_fs21 = __toESM(require("node:fs"), 1);
89745
89990
  var RESOLVE = { artboard: resolveBoardFile, document: resolveDocumentAsset, html: resolveHtmlFile };
89746
89991
  var isArtifactPage = (type, relative) => type === "html" ? relative === "index.html" : relative.endsWith(".html");
89747
89992
  function readArtifactFile(dir, type, relative, uiRuntimeUrl) {
@@ -89750,7 +89995,7 @@ function readArtifactFile(dir, type, relative, uiRuntimeUrl) {
89750
89995
  if (!target) return null;
89751
89996
  let data;
89752
89997
  try {
89753
- data = import_node_fs20.default.readFileSync(target);
89998
+ data = import_node_fs21.default.readFileSync(target);
89754
89999
  } catch {
89755
90000
  return null;
89756
90001
  }
@@ -89760,25 +90005,25 @@ function readArtifactFile(dir, type, relative, uiRuntimeUrl) {
89760
90005
  }
89761
90006
 
89762
90007
  // src/server/watch.ts
89763
- var import_node_fs21 = __toESM(require("node:fs"), 1);
89764
- var import_node_path18 = __toESM(require("node:path"), 1);
89765
- 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("/");
89766
90011
  var ignored = (file) => file === ".state" || file.startsWith(".state/");
89767
90012
  function snapshot(root, dir = root, out = /* @__PURE__ */ new Map()) {
89768
90013
  let entries2;
89769
90014
  try {
89770
- entries2 = import_node_fs21.default.readdirSync(dir, { withFileTypes: true });
90015
+ entries2 = import_node_fs22.default.readdirSync(dir, { withFileTypes: true });
89771
90016
  } catch {
89772
90017
  return out;
89773
90018
  }
89774
90019
  for (const entry of entries2) {
89775
- const full = import_node_path18.default.join(dir, entry.name);
89776
- 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));
89777
90022
  if (ignored(rel)) continue;
89778
90023
  if (entry.isDirectory()) snapshot(root, full, out);
89779
90024
  else {
89780
90025
  try {
89781
- out.set(rel, import_node_fs21.default.statSync(full).mtimeMs);
90026
+ out.set(rel, import_node_fs22.default.statSync(full).mtimeMs);
89782
90027
  } catch {
89783
90028
  }
89784
90029
  }
@@ -89811,7 +90056,7 @@ function watchTree(root, onChange, debounceMs = 120, backupPollMs = 1e3) {
89811
90056
  }, debounceMs);
89812
90057
  };
89813
90058
  try {
89814
- 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) => {
89815
90060
  if (filename) queue([toPosix(filename.toString())]);
89816
90061
  });
89817
90062
  watcher.on("error", () => {
@@ -89861,6 +90106,8 @@ var API_PREVIEW_ROUTE = /^\/api\/preview\/([^/]+)\/([^/]+)$/;
89861
90106
  var API_DOCUMENT_ROUTE = /^\/api\/document\/([^/]+)\/(state|update|comments)$/;
89862
90107
  var MAX_COMMENT_OP = 64 * 1024;
89863
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$/;
89864
90111
  var MAX_DOCUMENT_UPDATE = 8 * 1024 * 1024;
89865
90112
  var MAX_RELAY_FILE = 8 * 1024 * 1024;
89866
90113
  var MAX_RUNTIME_MESSAGE = 2048;
@@ -89879,17 +90126,17 @@ function titleOf(payload, ref) {
89879
90126
  return payload.canvas?.title || ref.id;
89880
90127
  }
89881
90128
  async function startArtifactsServer(options) {
89882
- const root = import_node_path19.default.resolve(options.root);
89883
- const viewerDir = import_node_path19.default.resolve(options.viewerDir);
89884
- const runtimeDir = options.runtimeDir ? import_node_path19.default.resolve(options.runtimeDir) : null;
89885
- const stateDir = import_node_path19.default.join(root, ".state");
89886
- import_node_fs22.default.mkdirSync(stateDir, { recursive: true, mode: 448 });
89887
- 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");
89888
90135
  if (options.configFile) ensureConfig(options.configFile);
89889
- const portFile = import_node_path19.default.join(stateDir, ".last-port");
89890
- 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");
89891
90138
  const token = readPersisted(tokenFile) || randomToken();
89892
- import_node_fs22.default.writeFileSync(tokenFile, `${token}
90139
+ import_node_fs23.default.writeFileSync(tokenFile, `${token}
89893
90140
  `, { mode: 384 });
89894
90141
  const contentToken = randomToken();
89895
90142
  const runtimeWarnings = /* @__PURE__ */ new Map();
@@ -89919,7 +90166,8 @@ async function startArtifactsServer(options) {
89919
90166
  direct.emit(message.artifact, message);
89920
90167
  };
89921
90168
  const avatars = createAvatars(options.fetch);
89922
- 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 });
89923
90171
  const documents = createDocumentRegistry({
89924
90172
  root,
89925
90173
  stateDir,
@@ -89957,8 +90205,8 @@ async function startArtifactsServer(options) {
89957
90205
  }
89958
90206
  return { dir, title, reasons, payload: published };
89959
90207
  };
89960
- 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;
89961
- 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;
89962
90210
  const sameOrigin = (req) => {
89963
90211
  const origin2 = req.headers.origin;
89964
90212
  const host = req.headers.host ?? "";
@@ -90037,7 +90285,7 @@ async function startArtifactsServer(options) {
90037
90285
  };
90038
90286
  const blockIdsOf = async (id2, dir) => {
90039
90287
  await (await documents.open(id2))?.flush();
90040
- 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"));
90041
90289
  return new Set(state.root.children.flatMap((block) => block.$?.blockId ? [block.$.blockId] : []));
90042
90290
  };
90043
90291
  const serveAgentComments = async (req, res, id2) => {
@@ -90077,6 +90325,55 @@ async function startArtifactsServer(options) {
90077
90325
  sendJson(res, 400, { error: err.message });
90078
90326
  }
90079
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
+ };
90080
90377
  const isAuthorized = (req, keyFromQuery) => keyFromQuery !== null && safeEqual(keyFromQuery, token) || safeEqual(parseCookies(req.headers.cookie)[cookieName] ?? "", token);
90081
90378
  const serveContent = (res, rest) => {
90082
90379
  if (rest.startsWith("runtime/")) {
@@ -90117,7 +90414,7 @@ async function startArtifactsServer(options) {
90117
90414
  sendFile(res, target, headers);
90118
90415
  return;
90119
90416
  }
90120
- import_node_fs22.default.readFile(target, "utf8", (err, source) => {
90417
+ import_node_fs23.default.readFile(target, "utf8", (err, source) => {
90121
90418
  if (err) send(res, 404, "Not found");
90122
90419
  else {
90123
90420
  const uiRuntimeUrl = runtimeDir ? `${contentPrefix}runtime/ryugi-ui.js` : void 0;
@@ -90128,6 +90425,7 @@ async function startArtifactsServer(options) {
90128
90425
  const artifactMeta = (ref, dir, contentOrigin = "") => ({
90129
90426
  ...readArtifact(dir, ref, runtimeOf(ref, dir)),
90130
90427
  ...ref.type !== "html" ? { agent: names.agent() } : {},
90428
+ ...ref.type === "artboard" ? { comments: boardComments.comments(ref.id), staleThreads: boardComments.stale(ref.id) } : {},
90131
90429
  contentBase: `${contentOrigin}${contentPrefix}${ref.type}/${ref.id}/`
90132
90430
  });
90133
90431
  const serveArtifactApi = (res, type, id2) => {
@@ -90146,7 +90444,7 @@ async function startArtifactsServer(options) {
90146
90444
  title: titleOf(readArtifact(entry.dir, entry), entry),
90147
90445
  updatedAt: latestMtime(entry.dir)
90148
90446
  }));
90149
- 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 });
90150
90448
  };
90151
90449
  function onClientText(socket, text2) {
90152
90450
  let message;
@@ -90208,7 +90506,7 @@ async function startArtifactsServer(options) {
90208
90506
  return;
90209
90507
  }
90210
90508
  if (pathname === "/robots.txt") {
90211
- sendFile(res, import_node_path19.default.join(viewerDir, "robots.txt"));
90509
+ sendFile(res, import_node_path20.default.join(viewerDir, "robots.txt"));
90212
90510
  return;
90213
90511
  }
90214
90512
  if (pathname.startsWith("/c/")) {
@@ -90290,6 +90588,24 @@ async function startArtifactsServer(options) {
90290
90588
  });
90291
90589
  return;
90292
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
+ }
90293
90609
  const documentMatch = API_DOCUMENT_ROUTE.exec(pathname);
90294
90610
  if (documentMatch) {
90295
90611
  void serveDocumentApi(req, res, documentMatch[1], documentMatch[2]).catch((err) => {
@@ -90305,7 +90621,7 @@ async function startArtifactsServer(options) {
90305
90621
  if (SHELL_ROUTE.test(pathname) || PREVIEW_ROUTE.test(pathname) || pathname === "/index.html") {
90306
90622
  const ref = artifactKeyOf(pathname.replace(/^\/(?:preview\/)?/, "").replace(/\/$/, ""));
90307
90623
  const dir = ref && resolveArtifactDir(root, ref.type, ref.id);
90308
- 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)));
90309
90625
  const title = ref ? dir ? titleOf(readArtifact(dir, ref), ref) : ref.id : `${project} \xB7 Artifacts`;
90310
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`;
90311
90627
  const canonical = `http://${urlHostForHttp(options.urlHost)}:${port}/${ref ? `${ref.type}/${ref.id}` : ""}`;
@@ -90314,7 +90630,7 @@ async function startArtifactsServer(options) {
90314
90630
  "Content-Security-Policy": PREVIEW_ROUTE.test(pathname) ? VIEWER_CSP.replace("frame-ancestors 'none'", "frame-ancestors 'self'") : VIEWER_CSP
90315
90631
  };
90316
90632
  if (key2 !== null) headers["Set-Cookie"] = `${cookieName}=${token}; HttpOnly; SameSite=Strict; Path=/`;
90317
- 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"));
90318
90634
  return;
90319
90635
  }
90320
90636
  const target = resolveInside(viewerDir, pathname.slice(1));
@@ -90373,7 +90689,7 @@ async function startArtifactsServer(options) {
90373
90689
  port = await listen(server, options.host, 0);
90374
90690
  }
90375
90691
  cookieName = `ryugi_artifacts_${port}`;
90376
- import_node_fs22.default.writeFileSync(portFile, `${port}
90692
+ import_node_fs23.default.writeFileSync(portFile, `${port}
90377
90693
  `, { mode: 384 });
90378
90694
  const origin = `http://${urlHostForHttp(options.urlHost)}:${port}`;
90379
90695
  const url = `${origin}/?key=${token}`;
@@ -90390,7 +90706,7 @@ async function startArtifactsServer(options) {
90390
90706
  return payload;
90391
90707
  };
90392
90708
  const refreshState = (ref, dir) => {
90393
- signatures.set(`${ref.type}/${ref.id}`, artifactSignature(dir));
90709
+ signatures.set(`${ref.type}/${ref.id}`, artifactSignature(dir, ref.type));
90394
90710
  return writeState(ref, dir);
90395
90711
  };
90396
90712
  const initialChanges = [];
@@ -90399,7 +90715,7 @@ async function startArtifactsServer(options) {
90399
90715
  initialChanges.push({ ref: entry, payload, files: [] });
90400
90716
  if (entry.type === "document") void documents.open(entry.id);
90401
90717
  }
90402
- 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 });
90403
90719
  const relayViewers = /* @__PURE__ */ new Map();
90404
90720
  const servedArtifacts = () => {
90405
90721
  const served = /* @__PURE__ */ new Map();
@@ -90432,6 +90748,11 @@ async function startArtifactsServer(options) {
90432
90748
  if (typeof request.message === "object" && request.message !== null) acceptReport({ ...request.message, artifact: key2 });
90433
90749
  return null;
90434
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
+ }
90435
90756
  if ((request.op === "state" || request.op === "update" || request.op === "comment") && ref.type !== "document") throw new Error(`${key2} is not a document`);
90436
90757
  if (request.op === "state" || request.op === "update") {
90437
90758
  const session = await documents.open(ref.id);
@@ -90496,7 +90817,9 @@ async function startArtifactsServer(options) {
90496
90817
  }
90497
90818
  if (ref.type === "document" && (changed.includes("doc.xml") || changed.length === 0)) documents.fileChanged(ref.id);
90498
90819
  if (ref.type === "document" && changed.includes("comments.json")) documents.commentsChanged(ref.id);
90499
- 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;
90500
90823
  autoPublisher?.changed(key2);
90501
90824
  runtimeWarnings.delete(key2);
90502
90825
  const payload = refreshState(ref, dir);
@@ -90540,7 +90863,7 @@ async function startArtifactsServer(options) {
90540
90863
  }
90541
90864
 
90542
90865
  // src/server/lifecycle.ts
90543
- var import_node_fs23 = __toESM(require("node:fs"), 1);
90866
+ var import_node_fs24 = __toESM(require("node:fs"), 1);
90544
90867
  var DEFAULT_IDLE_TIMEOUT_MS = 4 * 60 * 60 * 1e3;
90545
90868
  var DEFAULT_LIFECYCLE_CHECK_MS = 60 * 1e3;
90546
90869
  var SERVER_ID_ARG = "--artifacts-server-id=";
@@ -90572,7 +90895,7 @@ function parseEnv(env, argv = []) {
90572
90895
  function readOwners(file) {
90573
90896
  let text2;
90574
90897
  try {
90575
- text2 = import_node_fs23.default.readFileSync(file, "utf8");
90898
+ text2 = import_node_fs24.default.readFileSync(file, "utf8");
90576
90899
  } catch {
90577
90900
  return [];
90578
90901
  }
@@ -90635,20 +90958,20 @@ function openReady(payload) {
90635
90958
  }
90636
90959
 
90637
90960
  // package.json
90638
- var version = "1.15.0";
90961
+ var version = "1.15.2";
90639
90962
 
90640
90963
  // src/server/main.ts
90641
90964
  async function main() {
90642
90965
  const config = parseEnv(process.env, process.argv.slice(2));
90643
- const root = import_node_path20.default.resolve(config.root);
90644
- 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");
90645
90968
  const server = await startArtifactsServer({
90646
90969
  root,
90647
- viewerDir: import_node_path20.default.join(__dirname, "viewer"),
90648
- 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"),
90649
90972
  host: config.host,
90650
90973
  urlHost: config.urlHost,
90651
- 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"),
90652
90975
  cloud: { origin: config.cloudOrigin, credentialsFile: credentialsFile(process.env, process.platform, import_node_os.default.homedir()) },
90653
90976
  relay: { url: `${config.cloudOrigin.replace(/^http/, "ws")}/api/relay/host`, version }
90654
90977
  });
@@ -90678,12 +91001,12 @@ async function main() {
90678
91001
  if (stopping) return;
90679
91002
  stopping = true;
90680
91003
  console.log(JSON.stringify({ type: "server-stopped", reason }));
90681
- import_node_fs24.default.rmSync(import_node_path20.default.join(stateDir, "server-info"), { force: true });
90682
- 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() });
90683
91006
  await server.close();
90684
91007
  process.exit(0);
90685
91008
  };
90686
- const ownersFile = import_node_path20.default.join(stateDir, "owners");
91009
+ const ownersFile = import_node_path21.default.join(stateDir, "owners");
90687
91010
  const timer = setInterval(() => {
90688
91011
  const owners = readOwners(ownersFile);
90689
91012
  const reason = shutdownReason({
@@ -90697,7 +91020,7 @@ async function main() {
90697
91020
  timer.unref();
90698
91021
  process.on("SIGTERM", () => void shutdown("SIGTERM"));
90699
91022
  process.on("SIGINT", () => void shutdown("SIGINT"));
90700
- 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 });
90701
91024
  console.log(JSON.stringify({ type: "server-started", url: server.url, port: server.port, root }));
90702
91025
  }
90703
91026
  main().catch((err) => {