chalkbridge 1.0.4 → 1.1.1

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 (153) hide show
  1. package/dist/{chunk-U2ZMSGNS.js → chunk-XFH76JS6.js} +306 -11
  2. package/dist/cli.js +1346 -189
  3. package/dist/index.js +1 -1
  4. package/guides/icons.md +22 -3
  5. package/guides/layout.md +70 -4
  6. package/package.json +1 -1
  7. package/ui-dist/assets/{abnfDiagram-VCTEODGH-C_cqCKun.js → abnfDiagram-VCTEODGH-Bwcewgyl.js} +1 -1
  8. package/ui-dist/assets/{ar-SA-G6X2FPQ2-CEKRHtmX.js → ar-SA-G6X2FPQ2-58dCrzRZ.js} +1 -1
  9. package/ui-dist/assets/architecture-7GRP2DOG-Df8gKqbg.js +1 -0
  10. package/ui-dist/assets/{architectureDiagram-5GKGNRK7-BuWVonvB.js → architectureDiagram-5GKGNRK7-CMPSnIrP.js} +1 -1
  11. package/ui-dist/assets/{az-AZ-76LH7QW2-mn2Y6BCY.js → az-AZ-76LH7QW2-CCpEVexR.js} +1 -1
  12. package/ui-dist/assets/{bg-BG-XCXSNQG7-CraLx0fE.js → bg-BG-XCXSNQG7-DDlArZMH.js} +1 -1
  13. package/ui-dist/assets/{blockDiagram-NRAW4CY4-83XozRPe.js → blockDiagram-NRAW4CY4-D7TrKIo5.js} +1 -1
  14. package/ui-dist/assets/{bn-BD-2XOGV67Q-CsTrJsPu.js → bn-BD-2XOGV67Q-BXyhMN4V.js} +1 -1
  15. package/ui-dist/assets/{c4Diagram-UCG6FXSJ-DTdS8B8G.js → c4Diagram-UCG6FXSJ-CazStwlL.js} +1 -1
  16. package/ui-dist/assets/{ca-ES-6MX7JW3Y-DD8cv_ze.js → ca-ES-6MX7JW3Y-JHxjajfS.js} +1 -1
  17. package/ui-dist/assets/channel-C4uQlBVf.js +1 -0
  18. package/ui-dist/assets/{chunk-75Z2AOVW-DQDqkKP0.js → chunk-75Z2AOVW-BJsQGYsA.js} +1 -1
  19. package/ui-dist/assets/{chunk-DU6HZSFF-_YwywVCK.js → chunk-DU6HZSFF-C1xw9RZQ.js} +1 -1
  20. package/ui-dist/assets/{chunk-EIO257PC-CLwbKeso.js → chunk-EIO257PC-BrhyYOZD.js} +1 -1
  21. package/ui-dist/assets/{chunk-F27PBJKO-BwhryBf6.js → chunk-F27PBJKO-f6XMRQIS.js} +1 -1
  22. package/ui-dist/assets/{chunk-G27WJ6UU-BixSJdhz.js → chunk-G27WJ6UU-Cws9GsBU.js} +1 -1
  23. package/ui-dist/assets/{chunk-GMAD6QVW-Bp7aUQup.js → chunk-GMAD6QVW-CbhLp4Jt.js} +1 -1
  24. package/ui-dist/assets/{chunk-JQ64N6SF-Dt5bAScf.js → chunk-JQ64N6SF-B5lJbHea.js} +1 -1
  25. package/ui-dist/assets/{chunk-LCL6LL3I-B5MsvPPp.js → chunk-LCL6LL3I-bGYRjzHM.js} +1 -1
  26. package/ui-dist/assets/{chunk-OBVCFTLP-DBqcHzNF.js → chunk-OBVCFTLP-CxeUZPN4.js} +1 -1
  27. package/ui-dist/assets/{chunk-P2QGCYS3-TMRACTMO.js → chunk-P2QGCYS3-CZEugM1f.js} +1 -1
  28. package/ui-dist/assets/{chunk-POPQ4Y6H-BsmX_MmQ.js → chunk-POPQ4Y6H-CoDq2Q_g.js} +1 -1
  29. package/ui-dist/assets/{chunk-PWAF6VOD-g4MniF8u.js → chunk-PWAF6VOD-CX_HLotF.js} +1 -1
  30. package/ui-dist/assets/{chunk-R7TYR2AO-S7THFl9x.js → chunk-R7TYR2AO-B4M26mv9.js} +1 -1
  31. package/ui-dist/assets/{chunk-RHFEMEQ7-CZYull6D.js → chunk-RHFEMEQ7-CVXQBX33.js} +1 -1
  32. package/ui-dist/assets/{chunk-SVP7TREG-DCss0vmb.js → chunk-SVP7TREG-L7TJWT_o.js} +1 -1
  33. package/ui-dist/assets/{chunk-TEH6E4GO-B-6h5v_5.js → chunk-TEH6E4GO-Dct0SlmV.js} +1 -1
  34. package/ui-dist/assets/classDiagram-DTDB5LWJ-DRbrF0Ha.js +1 -0
  35. package/ui-dist/assets/classDiagram-v2-JRS7N3AN-DRbrF0Ha.js +1 -0
  36. package/ui-dist/assets/{cs-CZ-2BRQDIVT-C9fIM1z5.js → cs-CZ-2BRQDIVT-CvvJph-H.js} +1 -1
  37. package/ui-dist/assets/{cynefin-OW5HDTMX-CtdCt-Da.js → cynefin-OW5HDTMX-Ei_Xgi2_.js} +1 -1
  38. package/ui-dist/assets/{cynefinDiagram-5FMLGOSQ-CF9L7X85.js → cynefinDiagram-5FMLGOSQ-AFFSo_cm.js} +1 -1
  39. package/ui-dist/assets/{da-DK-5WZEPLOC-DXGP9-TU.js → da-DK-5WZEPLOC-BaezbVeN.js} +1 -1
  40. package/ui-dist/assets/{dagre-3AP2YEHR-Bt0GOj3f.js → dagre-3AP2YEHR-B-FVEWH2.js} +1 -1
  41. package/ui-dist/assets/{de-DE-XR44H4JA-ChR8kF4h.js → de-DE-XR44H4JA-BAGdMA-L.js} +1 -1
  42. package/ui-dist/assets/{diagram-S7CK7UJ4-D2NIL20U.js → diagram-S7CK7UJ4-eyl1yJj7.js} +1 -1
  43. package/ui-dist/assets/{diagram-UQ7AKVKN-Dty3qvRq.js → diagram-UQ7AKVKN-CugR7jCP.js} +1 -1
  44. package/ui-dist/assets/{diagram-VSXAHHWV-znpNlomZ.js → diagram-VSXAHHWV-Dz85hV5k.js} +1 -1
  45. package/ui-dist/assets/{diagram-VX7I27RA-DMKYB58y.js → diagram-VX7I27RA-BYSNxzTB.js} +1 -1
  46. package/ui-dist/assets/{diagram-Z3DM3KII-BLsAgfm1.js → diagram-Z3DM3KII-BN4pFJaP.js} +1 -1
  47. package/ui-dist/assets/{dist-Biz3NhE4.js → dist-CVqcfLn4.js} +4 -4
  48. package/ui-dist/assets/{ebnfDiagram-PWID7BFC-sN1WpIzH.js → ebnfDiagram-PWID7BFC-Ddprc5Rv.js} +1 -1
  49. package/ui-dist/assets/{el-GR-BZB4AONW-BMi-5oTn.js → el-GR-BZB4AONW-C8EbIpNQ.js} +1 -1
  50. package/ui-dist/assets/{en-B4ZKOASM-DTTcVPyw.js → en-B4ZKOASM-BZp0XUv6.js} +1 -1
  51. package/ui-dist/assets/{erDiagram-SSCWMZ5O-qLib13wp.js → erDiagram-SSCWMZ5O-BoMeaxjf.js} +1 -1
  52. package/ui-dist/assets/{es-ES-U4NZUMDT-DSk-whe2.js → es-ES-U4NZUMDT-CA2eQinb.js} +1 -1
  53. package/ui-dist/assets/{eu-ES-A7QVB2H4-Dlhf1HMz.js → eu-ES-A7QVB2H4-CcaNArBV.js} +1 -1
  54. package/ui-dist/assets/eventmodeling-NTZA5JFV-CSPN2jeA.js +1 -0
  55. package/ui-dist/assets/{fa-IR-HGAKTJCU-C4NL1SUQ.js → fa-IR-HGAKTJCU-BH7DgnZN.js} +1 -1
  56. package/ui-dist/assets/{fi-FI-Z5N7JZ37-BPDGFbjy.js → fi-FI-Z5N7JZ37-DWg6APJ2.js} +1 -1
  57. package/ui-dist/assets/flowDiagram-A5DVABFB-YrkFRLG9.js +1 -0
  58. package/ui-dist/assets/{fr-FR-RHASNOE6-BKM44_qc.js → fr-FR-RHASNOE6-qBJplDsT.js} +1 -1
  59. package/ui-dist/assets/{ganttDiagram-EL5Y4UJY-Cm_9m3AA.js → ganttDiagram-EL5Y4UJY-4wx5f9nv.js} +1 -1
  60. package/ui-dist/assets/{gitGraph-4MIJSDKK-B53Ccobw.js → gitGraph-4MIJSDKK-DDYlgTud.js} +1 -1
  61. package/ui-dist/assets/{gitGraphDiagram-WWUBYQGX-CshbIfrd.js → gitGraphDiagram-WWUBYQGX-DAhZkdQa.js} +1 -1
  62. package/ui-dist/assets/{gl-ES-HMX3MZ6V-CYq-Wgeo.js → gl-ES-HMX3MZ6V-CTvRbK-4.js} +1 -1
  63. package/ui-dist/assets/{he-IL-6SHJWFNN-CejOCh7c.js → he-IL-6SHJWFNN-w2aMYZZd.js} +1 -1
  64. package/ui-dist/assets/{hi-IN-IWLTKZ5I-DdZM6com.js → hi-IN-IWLTKZ5I-DX4ZRE2H.js} +1 -1
  65. package/ui-dist/assets/{hu-HU-A5ZG7DT2-DcLFRMuO.js → hu-HU-A5ZG7DT2-naHDPNOM.js} +1 -1
  66. package/ui-dist/assets/{id-ID-SAP4L64H-1x9JAf0A.js → id-ID-SAP4L64H-D5QxdnZa.js} +1 -1
  67. package/ui-dist/assets/image-GAAHSSAO-C9O0QZ3x.js +1 -0
  68. package/ui-dist/assets/{index-CzWe1zeF.js → index-W8DLsYN7.js} +8 -8
  69. package/ui-dist/assets/{info-A6RAGUB7-Cge9ecX_.js → info-A6RAGUB7-RXb_MEIH.js} +1 -1
  70. package/ui-dist/assets/{infoDiagram-RXCK75RN-VS1BTI_H.js → infoDiagram-RXCK75RN-DzaEiHqC.js} +1 -1
  71. package/ui-dist/assets/{ishikawaDiagram-5VMMS53U-Cbf1xbxj.js → ishikawaDiagram-5VMMS53U-C9We7rRC.js} +1 -1
  72. package/ui-dist/assets/{it-IT-JPQ66NNP-DgmQi0B_.js → it-IT-JPQ66NNP-B0YDAONu.js} +1 -1
  73. package/ui-dist/assets/{ja-JP-DBVTYXUO-BpmMfXE3.js → ja-JP-DBVTYXUO-CjS5c0my.js} +1 -1
  74. package/ui-dist/assets/{journeyDiagram-EYS64GPL-C8RWIfHV.js → journeyDiagram-EYS64GPL-BnolpCVx.js} +1 -1
  75. package/ui-dist/assets/{kaa-6HZHGXH3-CczLR43o.js → kaa-6HZHGXH3-BCxOG4kc.js} +1 -1
  76. package/ui-dist/assets/{kab-KAB-ZGHBKWFO-Cx74DVre.js → kab-KAB-ZGHBKWFO-D3QotDYI.js} +1 -1
  77. package/ui-dist/assets/{kanban-definition-3QL26DDD-R8ng9q7k.js → kanban-definition-3QL26DDD-Cy9HKz-0.js} +1 -1
  78. package/ui-dist/assets/{kk-KZ-P5N5QNE5-BPvg9KTT.js → kk-KZ-P5N5QNE5-BrSjotfG.js} +1 -1
  79. package/ui-dist/assets/{km-KH-HSX4SM5Z-Blgo7Zit.js → km-KH-HSX4SM5Z-fb8nqdkK.js} +1 -1
  80. package/ui-dist/assets/{ko-KR-MTYHY66A-DZdo-KOi.js → ko-KR-MTYHY66A-BXj8eDyy.js} +1 -1
  81. package/ui-dist/assets/{ku-TR-6OUDTVRD-CTpgWpW_.js → ku-TR-6OUDTVRD-COZArCj9.js} +1 -1
  82. package/ui-dist/assets/{line-Baf15v0x.js → line-7U1712L5.js} +1 -1
  83. package/ui-dist/assets/{lt-LT-XHIRWOB4-DKNkbcsj.js → lt-LT-XHIRWOB4-C-HJQqop.js} +1 -1
  84. package/ui-dist/assets/{lv-LV-5QDEKY6T-DwnZRxkH.js → lv-LV-5QDEKY6T-DLfr6jLA.js} +1 -1
  85. package/ui-dist/assets/{mermaid-parser.core-0V2gZHuN.js → mermaid-parser.core-DrLo74XG.js} +3 -3
  86. package/ui-dist/assets/{mindmap-definition-FBJOCRG2-7hOTFX4B.js → mindmap-definition-FBJOCRG2-Ba42gC8u.js} +1 -1
  87. package/ui-dist/assets/{mr-IN-CRQNXWMA-C-BFvdUk.js → mr-IN-CRQNXWMA-B9soDV3M.js} +1 -1
  88. package/ui-dist/assets/{my-MM-5M5IBNSE-BFs64Lrm.js → my-MM-5M5IBNSE-LZaFHx2a.js} +1 -1
  89. package/ui-dist/assets/{nb-NO-T6EIAALU-CHGzOQzI.js → nb-NO-T6EIAALU-BwhFAqXH.js} +1 -1
  90. package/ui-dist/assets/{nl-NL-IS3SIHDZ-CmrNJ43t.js → nl-NL-IS3SIHDZ-D2FhUB0E.js} +1 -1
  91. package/ui-dist/assets/{nn-NO-6E72VCQL-Cmx8XlD1.js → nn-NO-6E72VCQL-yn-NI0ZL.js} +1 -1
  92. package/ui-dist/assets/{oc-FR-POXYY2M6-D6ZK0W24.js → oc-FR-POXYY2M6-DOhix0u-.js} +1 -1
  93. package/ui-dist/assets/{pa-IN-N4M65BXN-CoFjVv1P.js → pa-IN-N4M65BXN-9aoDSwWx.js} +1 -1
  94. package/ui-dist/assets/{packet-AYTQ26CC-auec35xT.js → packet-AYTQ26CC-DuyZkxuT.js} +1 -1
  95. package/ui-dist/assets/{pegDiagram-XKGWAZYB-NMMkeAIs.js → pegDiagram-XKGWAZYB-PH6THd_o.js} +1 -1
  96. package/ui-dist/assets/percentages-BXMCSKIN-BFxN7npZ.js +1 -0
  97. package/ui-dist/assets/{pie-WAS4IAKB-Brhoju7F.js → pie-WAS4IAKB-BdQJyLvD.js} +1 -1
  98. package/ui-dist/assets/{pieDiagram-E7YTZNPT-CbaXV2ep.js → pieDiagram-E7YTZNPT-CPm011HM.js} +1 -1
  99. package/ui-dist/assets/{pl-PL-T2D74RX3-CtZh2-fJ.js → pl-PL-T2D74RX3-BO50j2s4.js} +1 -1
  100. package/ui-dist/assets/{pt-BR-5N22H2LF-Dwnm2Lle.js → pt-BR-5N22H2LF-Cp8T2vmD.js} +1 -1
  101. package/ui-dist/assets/{pt-PT-UZXXM6DQ-Cb08TgqC.js → pt-PT-UZXXM6DQ-CdTZJ0Y5.js} +1 -1
  102. package/ui-dist/assets/{quadrantDiagram-AXDQQJYC-CXEPKL_-.js → quadrantDiagram-AXDQQJYC-Cm1Rlzhk.js} +1 -1
  103. package/ui-dist/assets/{radar-RG4KPBEZ-CTDsgRBl.js → radar-RG4KPBEZ-48srnC1_.js} +1 -1
  104. package/ui-dist/assets/{railroad-74A4TZTK-Bad-j95k.js → railroad-74A4TZTK-C6601ICA.js} +1 -1
  105. package/ui-dist/assets/railroad-abnf-HS5TGJTU-Dmq5JjW8.js +1 -0
  106. package/ui-dist/assets/railroad-ebnf-LZEXJU2U-DzMo_BK8.js +1 -0
  107. package/ui-dist/assets/railroad-peg-WCYAUIDC-LanIHXaC.js +1 -0
  108. package/ui-dist/assets/{railroadDiagram-O6MQD6OU-CJiZ6mmg.js → railroadDiagram-O6MQD6OU-C8yR5TE7.js} +1 -1
  109. package/ui-dist/assets/{requirementDiagram-EFPCY7ZU-T8QMEZu6.js → requirementDiagram-EFPCY7ZU-DeleqP-5.js} +1 -1
  110. package/ui-dist/assets/{ro-RO-JPDTUUEW-D6H165NT.js → ro-RO-JPDTUUEW-C7SipvaK.js} +1 -1
  111. package/ui-dist/assets/{ru-RU-B4JR7IUQ-CBp0xVXG.js → ru-RU-B4JR7IUQ-dNLeoFpA.js} +1 -1
  112. package/ui-dist/assets/{sankeyDiagram-P5KCCOFB-Z9s0vmSt.js → sankeyDiagram-P5KCCOFB-CSJKN7--.js} +1 -1
  113. package/ui-dist/assets/{sequenceDiagram-WJ2MYXX4-BulR70nw.js → sequenceDiagram-WJ2MYXX4-B_C10dhq.js} +1 -1
  114. package/ui-dist/assets/{si-LK-N5RQ5JYF-10AxMH9e.js → si-LK-N5RQ5JYF-DogInl3-.js} +1 -1
  115. package/ui-dist/assets/{sk-SK-C5VTKIMK-BXvCxb46.js → sk-SK-C5VTKIMK-2YnR2SrM.js} +1 -1
  116. package/ui-dist/assets/{sl-SI-NN7IZMDC-BI5_PFXZ.js → sl-SI-NN7IZMDC-w1ecqU-U.js} +1 -1
  117. package/ui-dist/assets/{stateDiagram-HBIQ2CUA-Dam0DwKS.js → stateDiagram-HBIQ2CUA-BIYHmokV.js} +1 -1
  118. package/ui-dist/assets/stateDiagram-v2-4QOOHH4V-BEVYrEze.js +1 -0
  119. package/ui-dist/assets/subset-shared.chunk-zuCoK3Ax.js +1 -0
  120. package/ui-dist/assets/{subset-worker.chunk-BaDYCnkg.js → subset-worker.chunk-Fq9aq3Pk.js} +1 -1
  121. package/ui-dist/assets/{sv-SE-XGPEYMSR-dVM3morJ.js → sv-SE-XGPEYMSR-ZA1w16aZ.js} +1 -1
  122. package/ui-dist/assets/{swimlanes-XN3QIQJK-txWA6rWV.js → swimlanes-XN3QIQJK-Bsz-wFaq.js} +1 -1
  123. package/ui-dist/assets/swimlanesDiagram-VK2B7HYN-DVXdrFIL.js +8 -0
  124. package/ui-dist/assets/{ta-IN-2NMHFXQM-CWJY03pN.js → ta-IN-2NMHFXQM-CanbC1P6.js} +1 -1
  125. package/ui-dist/assets/{th-TH-HPSO5L25-CR6GhLgj.js → th-TH-HPSO5L25-DyHWEX0G.js} +1 -1
  126. package/ui-dist/assets/{timeline-definition-24CTP7MA-D8m_1UrT.js → timeline-definition-24CTP7MA-4TgC1OoH.js} +1 -1
  127. package/ui-dist/assets/{tr-TR-DEFEU3FU-BI8tDJSm.js → tr-TR-DEFEU3FU-D0W6rufc.js} +1 -1
  128. package/ui-dist/assets/{treeView-Q6P3EWNA-CWJmpg6A.js → treeView-Q6P3EWNA-C9fqdzP-.js} +1 -1
  129. package/ui-dist/assets/{treemap-WGGIJYW6-CvClsvN5.js → treemap-WGGIJYW6-BqNZy40L.js} +1 -1
  130. package/ui-dist/assets/{uk-UA-QMV73CPH-D3Uc38wP.js → uk-UA-QMV73CPH-C1iCWdAQ.js} +1 -1
  131. package/ui-dist/assets/{vennDiagram-4TSXK5OY-C5iS8F8o.js → vennDiagram-4TSXK5OY-BQFCZXqB.js} +1 -1
  132. package/ui-dist/assets/{vi-VN-M7AON7JQ-B6BZpyS4.js → vi-VN-M7AON7JQ-BWynaJhA.js} +1 -1
  133. package/ui-dist/assets/{wardley-WFR3VGLG-CdqB7_9-.js → wardley-WFR3VGLG-cNQ06Oix.js} +1 -1
  134. package/ui-dist/assets/{wardleyDiagram-VM6X3IG4-DAHyWwbW.js → wardleyDiagram-VM6X3IG4-BbLDW2ka.js} +1 -1
  135. package/ui-dist/assets/{xychartDiagram-S5SC5T6Z-ilyFTLQ_.js → xychartDiagram-S5SC5T6Z-r5VAgI1x.js} +1 -1
  136. package/ui-dist/assets/{zh-CN-LNUGB5OW-B0PN355q.js → zh-CN-LNUGB5OW-BNvy0Mnj.js} +1 -1
  137. package/ui-dist/assets/{zh-HK-E62DVLB3-DUEHDaPB.js → zh-HK-E62DVLB3-Csv83LC3.js} +1 -1
  138. package/ui-dist/assets/{zh-TW-RAJ6MFWO-Ci4YMClQ.js → zh-TW-RAJ6MFWO-CW-6XIyO.js} +1 -1
  139. package/ui-dist/index.html +1 -1
  140. package/ui-dist/assets/architecture-7GRP2DOG-fqG4giDT.js +0 -1
  141. package/ui-dist/assets/channel-BOrwYBHk.js +0 -1
  142. package/ui-dist/assets/classDiagram-DTDB5LWJ-Dm2kcSSm.js +0 -1
  143. package/ui-dist/assets/classDiagram-v2-JRS7N3AN-Dm2kcSSm.js +0 -1
  144. package/ui-dist/assets/eventmodeling-NTZA5JFV-BN91GLT5.js +0 -1
  145. package/ui-dist/assets/flowDiagram-A5DVABFB-DBrXJ-Jf.js +0 -1
  146. package/ui-dist/assets/image-GAAHSSAO-BgehxCDd.js +0 -1
  147. package/ui-dist/assets/percentages-BXMCSKIN-BBuUnwM-.js +0 -1
  148. package/ui-dist/assets/railroad-abnf-HS5TGJTU-CbvNSNpm.js +0 -1
  149. package/ui-dist/assets/railroad-ebnf-LZEXJU2U-DdltjnE0.js +0 -1
  150. package/ui-dist/assets/railroad-peg-WCYAUIDC-CgbxToYh.js +0 -1
  151. package/ui-dist/assets/stateDiagram-v2-4QOOHH4V-CSjCWsxW.js +0 -1
  152. package/ui-dist/assets/subset-shared.chunk-Du7WrYQy.js +0 -1
  153. package/ui-dist/assets/swimlanesDiagram-VK2B7HYN-CU4h0g7a.js +0 -8
package/dist/cli.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { SceneElement, PackName, PackKind, IconId, SceneId, Scene, SnapshotId, SERVER_MANAGED_FIELDS, BINDING_FIELDS, GUIDE_TOPICS, DetailLevel, createNodeSkeletonExpander, ok, fail, bridgeSpawnFailed, bridgeVersionMismatch, bridgeUnreachable, GuideTopic, storeUnavailable, describeThrown, unreadableSceneFile, pathOutsideRepo, packInstallFailed, sceneFileMissing, notAPack, PackRecipe, portUnavailable, DescribeSceneOutput, DescribeSceneInput, GetSelectionOutput, GetSelectionInput, GetElementsOutput, GetElementsInput, ListScenesOutput, ListScenesInput, SceneSettingsOutput, SceneSettingsInput, SearchIconsOutput, SearchIconsInput, ScreenshotOutput, ScreenshotInput, ReadMeOutput, ReadMeInput, PlaceIconOutput, PlaceIconInput, SnapshotOutput, SnapshotInput, RestoreSnapshotOutput, RestoreSnapshotInput, ExportSceneOutput, ExportSceneInput, ImportSceneOutput, ImportSceneInput, ExportImageOutput, ExportImageInput, InstallPackOutput, InstallPackInput, notARepo, ScreenshotResponse, RemovePackOutput, ListPacksOutput, RawElementsOutput, FocusOutput, EmbedOutput, AnnotateOutput, FrameOutput, UngroupOutput, GroupOutput, ArrangeOutput, DeleteElementsOutput, UpdateElementsOutput, ConnectOutput, CreateElementsOutput, HealthResponse, iconIdOf, unknownScene, unknownSnapshot, exportFailed, unknownIcon, HOUSE, FileId, ElementId, diskConflict, refusedFieldMessage, idCollision, expansionFailed, emptySelection, unknownElement, frameIdOf, GroupId, staleVersion, noBoundLabel, deepEqual, exportTimedOut, noBrowser, DebugElementRequest, CreateElementsInput, ConnectToolInput, UpdateElementsInput, DeleteElementsInput, ArrangeToolInput, GroupInput, UngroupInput, FrameInput, AnnotateInput, EmbedInput, FocusInput, RawElementsInput, ListPacksInput, RemovePackInput, DataUrl, Index, ROUNDNESS, STALE_SELECTION_MS, noScenes, ClientMessage, assertNever, PROTOCOL_VERSION, ApiError, EXCALIDRAW_FILE_VERSION, EXCALIDRAW_FILE_TYPE, point, FLAG_GLYPH, KIND_ABBREVIATION, catalogUnavailable, FrameId, IconItem, ExcalidrawFile } from './chunk-U2ZMSGNS.js';
2
+ import { SceneElement, PackName, PackKind, IconId, SceneId, Scene, SnapshotId, SERVER_MANAGED_FIELDS, BINDING_FIELDS, GUIDE_TOPICS, DetailLevel, createNodeSkeletonExpander, ok, fail, bridgeSpawnFailed, bridgeVersionMismatch, bridgeUnreachable, GuideTopic, storeUnavailable, describeThrown, unreadableSceneFile, pathOutsideRepo, packInstallFailed, sceneFileMissing, notAPack, PackRecipe, portUnavailable, DescribeSceneOutput, DescribeSceneInput, GetSelectionOutput, GetSelectionInput, GetElementsOutput, GetElementsInput, ListScenesOutput, ListScenesInput, SceneSettingsOutput, SceneSettingsInput, SearchIconsOutput, SearchIconsInput, ScreenshotOutput, ScreenshotInput, ReadMeOutput, ReadMeInput, PlaceIconOutput, PlaceIconInput, SnapshotOutput, SnapshotInput, RestoreSnapshotOutput, RestoreSnapshotInput, ExportSceneOutput, ExportSceneInput, ImportSceneOutput, ImportSceneInput, ExportImageOutput, ExportImageInput, InstallPackOutput, InstallPackInput, notARepo, ScreenshotResponse, RemovePackOutput, ListPacksOutput, RawElementsOutput, FocusOutput, EmbedOutput, AnnotateOutput, FrameOutput, UngroupOutput, GroupOutput, ArrangeOutput, DeleteElementsOutput, UpdateElementsOutput, ConnectOutput, CreateElementsOutput, HealthResponse, iconIdOf, unknownScene, unknownSnapshot, exportFailed, unknownIcon, HOUSE, FileId, ElementId, diskConflict, refusedFieldMessage, idCollision, expansionFailed, emptySelection, unknownElement, measureLine, frameIdOf, GroupId, staleVersion, noBoundLabel, deepEqual, exportTimedOut, noBrowser, DebugElementRequest, CreateElementsInput, ConnectToolInput, UpdateElementsInput, DeleteElementsInput, ArrangeToolInput, GroupInput, UngroupInput, FrameInput, AnnotateInput, EmbedInput, FocusInput, RawElementsInput, ListPacksInput, RemovePackInput, iconNeedsAPlace, DataUrl, Index, wrapLines, ROUNDNESS, STALE_SELECTION_MS, noScenes, ClientMessage, assertNever, PROTOCOL_VERSION, ApiError, EXCALIDRAW_FILE_VERSION, EXCALIDRAW_FILE_TYPE, point, FLAG_GLYPH, KIND_ABBREVIATION, catalogUnavailable, FrameId, IconItem, ExcalidrawFile } from './chunk-XFH76JS6.js';
3
3
  import './chunk-7D4SUZUM.js';
4
4
  import { appendFileSync, existsSync, mkdirSync } from 'node:fs';
5
5
  import { homedir } from 'node:os';
@@ -2025,6 +2025,254 @@ function createExportBroker(options, deps) {
2025
2025
  };
2026
2026
  }
2027
2027
 
2028
+ // src/domain/scene/overlaps.ts
2029
+ var MIN_OVERLAP_PX = 2;
2030
+ var MAX_REPORTED_OVERLAPS = 12;
2031
+ function routeMidpoint(arrow) {
2032
+ const points = arrow.points;
2033
+ const lengths = points.slice(1).map((p, i) => {
2034
+ const prev = points[i];
2035
+ const [px2, py] = p;
2036
+ if (prev === void 0 || px2 === void 0 || py === void 0) return 0;
2037
+ const [qx, qy] = prev;
2038
+ if (qx === void 0 || qy === void 0) return 0;
2039
+ return Math.hypot(px2 - qx, py - qy);
2040
+ });
2041
+ let want = lengths.reduce((sum, l) => sum + l, 0) / 2;
2042
+ for (let i = 0; i < lengths.length; i++) {
2043
+ const from = points[i];
2044
+ const to = points[i + 1];
2045
+ const length = lengths[i];
2046
+ if (from === void 0 || to === void 0 || length === void 0) continue;
2047
+ const [fx, fy] = from;
2048
+ const [tx, ty] = to;
2049
+ if (fx === void 0 || fy === void 0 || tx === void 0 || ty === void 0) continue;
2050
+ if (want > length && i < lengths.length - 1) {
2051
+ want -= length;
2052
+ continue;
2053
+ }
2054
+ const k = length === 0 ? 0 : want / length;
2055
+ return { x: arrow.x + fx + (tx - fx) * k, y: arrow.y + fy + (ty - fy) * k };
2056
+ }
2057
+ return { x: arrow.x, y: arrow.y };
2058
+ }
2059
+ function renderedRect(element, byId) {
2060
+ if (element.type !== "text" || element.containerId === null) return element;
2061
+ const container = byId.get(element.containerId);
2062
+ if (container === void 0 || container.type !== "arrow") return element;
2063
+ const mid = routeMidpoint(container);
2064
+ return {
2065
+ x: mid.x - element.width / 2,
2066
+ y: mid.y - element.height / 2,
2067
+ width: element.width,
2068
+ height: element.height
2069
+ };
2070
+ }
2071
+ var intersection = (a, b) => ({
2072
+ width: Math.min(a.x + a.width, b.x + b.width) - Math.max(a.x, b.x),
2073
+ height: Math.min(a.y + a.height, b.y + b.height) - Math.max(a.y, b.y)
2074
+ });
2075
+ function rectsOverlap(a, b) {
2076
+ const { width, height } = intersection(a, b);
2077
+ return width >= MIN_OVERLAP_PX && height >= MIN_OVERLAP_PX;
2078
+ }
2079
+ function frameOf(element, byId) {
2080
+ if (element.frameId !== null) return element.frameId;
2081
+ if (element.type !== "text" || element.containerId === null) return null;
2082
+ return byId.get(element.containerId)?.frameId ?? null;
2083
+ }
2084
+ function nested(a, b, byId) {
2085
+ if (a.type === "frame" && frameOf(b, byId) === frameIdOf(a.id)) return true;
2086
+ if (b.type === "frame" && frameOf(a, byId) === frameIdOf(b.id)) return true;
2087
+ if (a.type === "text" && a.containerId === b.id && b.type !== "arrow") return true;
2088
+ if (b.type === "text" && b.containerId === a.id && a.type !== "arrow") return true;
2089
+ return false;
2090
+ }
2091
+ var participates = (element) => !element.isDeleted && element.type !== "arrow" && element.type !== "line" && // Frames are regions, not shapes. One is 600 px across, so nearly everything near it
2092
+ // intersects its area — and unlike a box it does not obscure what it covers, because a
2093
+ // frame only clips its *own* children. Reporting non-children put a warning on every
2094
+ // framed diagram, which is the noise this file exists to avoid. The case that genuinely
2095
+ // hides content — a child outside the frame holding it — is prevented rather than
2096
+ // reported: `frames.refitFrames` grows the frame on every path that can move a child.
2097
+ element.type !== "frame" && element.width > 0 && element.height > 0;
2098
+ function wouldCollide(candidate, elements, ignore = /* @__PURE__ */ new Set()) {
2099
+ const byId = new Map(elements.map((element) => [element.id, element]));
2100
+ return elements.filter(participates).filter((element) => !ignore.has(element.id)).some((element) => rectsOverlap(candidate, renderedRect(element, byId)));
2101
+ }
2102
+ function findOverlaps(elements) {
2103
+ const byId = new Map(elements.map((element) => [element.id, element]));
2104
+ const live = elements.filter(participates);
2105
+ const found = [];
2106
+ for (let i = 0; i < live.length; i++) {
2107
+ const a = live[i];
2108
+ if (a === void 0) continue;
2109
+ for (let j = i + 1; j < live.length; j++) {
2110
+ const b = live[j];
2111
+ if (b === void 0) continue;
2112
+ if (nested(a, b, byId)) continue;
2113
+ const { width, height } = intersection(renderedRect(a, byId), renderedRect(b, byId));
2114
+ if (width < MIN_OVERLAP_PX || height < MIN_OVERLAP_PX) continue;
2115
+ found.push({ a: a.id, b: b.id, width: Math.round(width), height: Math.round(height) });
2116
+ }
2117
+ }
2118
+ return found.sort(
2119
+ (p, q) => q.width * q.height - p.width * p.height || (p.a < q.a ? -1 : p.a > q.a ? 1 : 0) || (p.b < q.b ? -1 : p.b > q.b ? 1 : 0)
2120
+ );
2121
+ }
2122
+ function overlapWarning(elements) {
2123
+ const found = findOverlaps(elements);
2124
+ const worst = found[0];
2125
+ if (worst === void 0) return null;
2126
+ const size = `${Math.round(worst.width)}x${Math.round(worst.height)}`;
2127
+ const count2 = found.length === 1 ? "1 overlap" : `${found.length} overlaps`;
2128
+ return `${count2} on the canvas \u2014 worst ${worst.a} \xD7 ${worst.b} (${size}). chalk_describe_scene lists them; chalk_arrange moves them apart`;
2129
+ }
2130
+
2131
+ // src/domain/scene/anchors.ts
2132
+ var ANCHOR_GAP = 12;
2133
+ var anchorsOf = (element) => element.customData?.chalk?.anchorIds ?? [];
2134
+ var isAnchored = (element) => anchorsOf(element).length > 0;
2135
+ function anchoredPosition(anchor, size, placement) {
2136
+ const midX = anchor.x + anchor.width / 2 - size.width / 2;
2137
+ const midY = anchor.y + anchor.height / 2 - size.height / 2;
2138
+ switch (placement) {
2139
+ case "above":
2140
+ return { x: midX, y: anchor.y - size.height - ANCHOR_GAP };
2141
+ case "below":
2142
+ return { x: midX, y: anchor.y + anchor.height + ANCHOR_GAP };
2143
+ case "left":
2144
+ return { x: anchor.x - size.width - ANCHOR_GAP, y: midY };
2145
+ case "right":
2146
+ return { x: anchor.x + anchor.width + ANCHOR_GAP, y: midY };
2147
+ case "badge":
2148
+ return { x: anchor.x + anchor.width - size.width / 2, y: anchor.y - size.height / 2 };
2149
+ case "inside":
2150
+ return { x: midX, y: midY };
2151
+ }
2152
+ }
2153
+ var captionBelow = (position, size) => ({
2154
+ x: position.x,
2155
+ y: position.y + size.height + 8
2156
+ });
2157
+ function withAnchoredElements(scene, deltas) {
2158
+ const attached = scene.filter((element) => !element.isDeleted && isAnchored(element));
2159
+ if (attached.length === 0) return deltas;
2160
+ const widened = new Map(deltas);
2161
+ const present2 = new Set(scene.filter((element) => !element.isDeleted).map((e) => e.id));
2162
+ for (const element of attached) {
2163
+ if (widened.has(element.id)) continue;
2164
+ const live = anchorsOf(element).filter((id) => present2.has(id));
2165
+ if (live.length === 0) continue;
2166
+ const moves = live.map((id) => deltas.get(id));
2167
+ const [first] = moves;
2168
+ if (first === void 0) continue;
2169
+ if (!moves.every((move) => move !== void 0 && move[0] === first[0] && move[1] === first[1])) {
2170
+ continue;
2171
+ }
2172
+ widened.set(element.id, first);
2173
+ }
2174
+ return widened;
2175
+ }
2176
+
2177
+ // src/domain/scene/recommend.ts
2178
+ var ENOUGH_TO_MATTER = 5;
2179
+ var HUB_SHARE = 0.5;
2180
+ var HUB_MIN_DEGREE = 4;
2181
+ var RING_LABEL_WIDTH = 200;
2182
+ function recommendLayout(scene, targetIds) {
2183
+ const wanted = new Set(targetIds);
2184
+ const nodes = scene.filter(
2185
+ (element) => wanted.has(element.id) && !isLinear(element) && !isAnchored(element) && !element.isDeleted
2186
+ );
2187
+ if (nodes.length < ENOUGH_TO_MATTER) return null;
2188
+ const ids = new Set(nodes.map((node) => node.id));
2189
+ const degree = new Map(nodes.map((node) => [node.id, 0]));
2190
+ const out = new Map(nodes.map((node) => [node.id, []]));
2191
+ let edges = 0;
2192
+ for (const element of scene) {
2193
+ if (element.type !== "arrow" || element.isDeleted) continue;
2194
+ const from = startOf(element);
2195
+ const to = endOf(element);
2196
+ if (from === null || to === null || from === to) continue;
2197
+ if (!ids.has(from) || !ids.has(to)) continue;
2198
+ edges += 1;
2199
+ degree.set(from, (degree.get(from) ?? 0) + 1);
2200
+ degree.set(to, (degree.get(to) ?? 0) + 1);
2201
+ out.get(from)?.push(to);
2202
+ }
2203
+ if (edges === 0) {
2204
+ return {
2205
+ op: "grid",
2206
+ why: `${nodes.length} elements with no arrows between them \u2014 chalk_arrange(op: "grid") is the layout for a set rather than a graph`
2207
+ };
2208
+ }
2209
+ const busiest = Math.max(...degree.values());
2210
+ const hubShare = busiest / (nodes.length - 1);
2211
+ const depth = longestChain(nodes, out);
2212
+ const widestLabel = Math.max(...nodes.map((node) => node.width));
2213
+ if (hubShare >= HUB_SHARE && busiest >= HUB_MIN_DEGREE) {
2214
+ const hub = nodes.find((node) => (degree.get(node.id) ?? 0) === busiest);
2215
+ const name = hub === void 0 ? "one element" : shortLabel(scene, hub);
2216
+ return widestLabel > RING_LABEL_WIDTH ? {
2217
+ op: "layout-tree",
2218
+ why: `${name} touches ${busiest} of ${nodes.length - 1} others and the labels are wide \u2014 chalk_arrange(op: "layout-tree") puts it on the left and fans the rest out; a ring this wide crosses its own middle`
2219
+ } : {
2220
+ op: "layout-radial",
2221
+ why: `${name} touches ${busiest} of ${nodes.length - 1} others \u2014 chalk_arrange(op: "layout-radial") puts it at the centre, which ranks cannot show`
2222
+ };
2223
+ }
2224
+ return {
2225
+ op: "layout-layered",
2226
+ why: `${nodes.length} elements, ${edges} arrows, longest chain ${depth} \u2014 chalk_arrange(op: "layout-layered") ranks them by the direction of the arrows`
2227
+ };
2228
+ }
2229
+ function longestChain(nodes, out) {
2230
+ const ON_STACK = 1;
2231
+ const DONE = 2;
2232
+ const state = /* @__PURE__ */ new Map();
2233
+ const depth = /* @__PURE__ */ new Map();
2234
+ const visit = (start) => {
2235
+ const stack = [{ node: start, cursor: 0 }];
2236
+ state.set(start, ON_STACK);
2237
+ while (stack.length > 0) {
2238
+ const frame = stack[stack.length - 1];
2239
+ if (frame === void 0) break;
2240
+ const kids = out.get(frame.node) ?? [];
2241
+ if (frame.cursor >= kids.length) {
2242
+ let best = 1;
2243
+ for (const kid2 of kids) {
2244
+ if (state.get(kid2) === DONE) best = Math.max(best, 1 + (depth.get(kid2) ?? 0));
2245
+ }
2246
+ depth.set(frame.node, best);
2247
+ state.set(frame.node, DONE);
2248
+ stack.pop();
2249
+ continue;
2250
+ }
2251
+ const kid = kids[frame.cursor];
2252
+ frame.cursor += 1;
2253
+ if (kid === void 0 || state.has(kid)) continue;
2254
+ state.set(kid, ON_STACK);
2255
+ stack.push({ node: kid, cursor: 0 });
2256
+ }
2257
+ return depth.get(start) ?? 1;
2258
+ };
2259
+ let longest = 0;
2260
+ for (const node of [...nodes].sort((a, b) => a.id < b.id ? -1 : 1)) {
2261
+ if (!state.has(node.id)) visit(node.id);
2262
+ longest = Math.max(longest, depth.get(node.id) ?? 1);
2263
+ }
2264
+ return longest;
2265
+ }
2266
+ function shortLabel(scene, element) {
2267
+ const bound = (element.boundElements ?? []).find((b) => b.type === "text")?.id;
2268
+ const caption = bound === void 0 ? void 0 : scene.find((e) => e.id === bound);
2269
+ if (caption !== void 0 && caption.type === "text") {
2270
+ const text = (caption.originalText || caption.text).replace(/\n/g, " ").trim();
2271
+ if (text !== "") return `"${text}"`;
2272
+ }
2273
+ return String(element.id).slice(0, 4);
2274
+ }
2275
+
2028
2276
  // src/domain/scene/observe.ts
2029
2277
  var SHORT_ID_LENGTH = 4;
2030
2278
  var shortId2 = (id) => id.slice(0, SHORT_ID_LENGTH);
@@ -2131,6 +2379,7 @@ function observeScene(input) {
2131
2379
  const selected = new Set(input.selectedIds ?? []);
2132
2380
  const context = { labels, frames, selected, detail: input.detail };
2133
2381
  const rows = rowElements(scene.elements).filter((element) => element.type !== "frame");
2382
+ const overlaps = findOverlaps(scene.elements);
2134
2383
  return {
2135
2384
  sceneId: scene.id,
2136
2385
  name: scene.name,
@@ -2139,6 +2388,14 @@ function observeScene(input) {
2139
2388
  updatedAgoMs: Math.max(0, input.now - scene.updatedAt),
2140
2389
  frames: [...frames.values()],
2141
2390
  elements: rows.map((element) => describeElement(element, context)),
2391
+ overlaps: overlaps.slice(0, MAX_REPORTED_OVERLAPS),
2392
+ overlapCount: overlaps.length,
2393
+ // Over the rows, not the raw elements: a caption is part of the box it names, and
2394
+ // counting one as a node would halve every degree the advice is computed from.
2395
+ layoutAdvice: recommendLayout(
2396
+ scene.elements,
2397
+ rows.map((element) => element.id)
2398
+ )?.why ?? null,
2142
2399
  detail: input.detail
2143
2400
  };
2144
2401
  }
@@ -3171,6 +3428,10 @@ function restoreSnapshot(deps) {
3171
3428
  sceneId: scene.id,
3172
3429
  sceneVersion: updated.sceneVersion,
3173
3430
  broadcastTo,
3431
+ // A restore puts back exactly what was saved, so any collision in it is the one the
3432
+ // snapshot was taken with. Worth saying: the reader is often restoring *because* a
3433
+ // layout went wrong, and silence here would read as "the old one was clean".
3434
+ overlapWarning: overlapWarning(snapshot.elements),
3174
3435
  snapshotId: snapshot.summary.id,
3175
3436
  label: snapshot.summary.label,
3176
3437
  restored: snapshot.elements.filter((element) => !element.isDeleted).length
@@ -3195,9 +3456,122 @@ function serviceSize() {
3195
3456
  const [width, height] = HOUSE.sizes.service;
3196
3457
  return { width, height };
3197
3458
  }
3459
+ var LINE_HEIGHT = 1.25;
3460
+ var quantise = (value) => Math.ceil(value / HOUSE.grid) * HOUSE.grid;
3461
+ function labelledSize(label, fontSize = HOUSE.fontSize) {
3462
+ const floor = serviceSize();
3463
+ if (label === void 0 || label === "") return floor;
3464
+ const padX = HOUSE.sizes.labelPadX * 2;
3465
+ const padY = HOUSE.sizes.labelPadY * 2;
3466
+ const oneLine = measureLine(label, fontSize) + padX;
3467
+ const width = Math.max(floor.width, quantise(Math.min(oneLine, HOUSE.sizes.maxBox)));
3468
+ const lines = wrapLines(label, fontSize, width - padX);
3469
+ const height = Math.max(floor.height, quantise(lines.length * fontSize * LINE_HEIGHT + padY));
3470
+ return { width, height };
3471
+ }
3472
+
3473
+ // src/domain/scene/skeleton.ts
3474
+ var isContainer = (type) => type === "rectangle" || type === "diamond" || type === "ellipse";
3475
+ function explicit(input) {
3476
+ const kept = {};
3477
+ for (const [key, value] of Object.entries(input)) {
3478
+ if (value === void 0) continue;
3479
+ if (key === "type" || key === "id" || key === "role" || key === "label") continue;
3480
+ kept[key] = value;
3481
+ }
3482
+ return kept;
3483
+ }
3484
+ var defaultSize = (element) => labelledSize(element.label, element.fontSize ?? HOUSE.fontSize);
3485
+ function noteWrap(text, fontSize, requested) {
3486
+ const column = requested ?? HOUSE.sizes.note;
3487
+ if (text === "") return { text };
3488
+ if (requested === void 0 && measureLine(text, fontSize) <= column) return { text };
3489
+ const lines = wrapLines(text, fontSize, column);
3490
+ return { text: lines.join("\n"), width: column, autoResize: false };
3491
+ }
3492
+ function normalizeElements(input) {
3493
+ return input.elements.map((element, i) => {
3494
+ const fallback = input.ids[i];
3495
+ if (fallback === void 0) {
3496
+ throw new Error(`normalizeElements needs one id per element: none at position ${i}`);
3497
+ }
3498
+ const id = element.id ?? fallback;
3499
+ const role = styleForRole(element.role ?? "compute");
3500
+ const set = explicit(element);
3501
+ if (element.type === "text") {
3502
+ const text = element.text ?? element.label ?? "";
3503
+ const skeleton2 = {
3504
+ type: "text",
3505
+ id,
3506
+ x: element.x,
3507
+ y: element.y,
3508
+ fontSize: HOUSE.fontSize,
3509
+ fontFamily: HOUSE.fontFamily,
3510
+ // A free-standing note is not a service box: it takes the role's ink but never
3511
+ // its fill, because a filled rectangle behind a caption reads as a shape.
3512
+ strokeColor: role.strokeColor,
3513
+ ...set,
3514
+ // Last, because it owns `text`: `explicit` copies the caller's unwrapped string
3515
+ // across and this is what replaces it.
3516
+ ...noteWrap(text, element.fontSize ?? HOUSE.fontSize, element.width)
3517
+ };
3518
+ return skeleton2;
3519
+ }
3520
+ if (element.type === "line") {
3521
+ const skeleton2 = {
3522
+ type: "line",
3523
+ id,
3524
+ x: element.x,
3525
+ y: element.y,
3526
+ strokeColor: role.strokeColor,
3527
+ strokeWidth: HOUSE.strokeWidth,
3528
+ roughness: HOUSE.roughness,
3529
+ ...set,
3530
+ ...element.label === void 0 ? {} : { label: houseLabel(element.label) }
3531
+ };
3532
+ return skeleton2;
3533
+ }
3534
+ if (!isContainer(element.type)) {
3535
+ throw new Error(`normalizeElements cannot build a ${element.type}`);
3536
+ }
3537
+ const skeleton = {
3538
+ type: element.type,
3539
+ id,
3540
+ x: element.x,
3541
+ y: element.y,
3542
+ ...defaultSize(element),
3543
+ ...role,
3544
+ ...set,
3545
+ ...element.label === void 0 ? {} : { label: houseLabel(element.label) }
3546
+ };
3547
+ return skeleton;
3548
+ });
3549
+ }
3550
+ var LAYOUT_HINT_THRESHOLD = 4;
3551
+ function layoutHint(elements) {
3552
+ if (elements.length < LAYOUT_HINT_THRESHOLD) return null;
3553
+ const positions = new Set(elements.map((element) => `${element.x},${element.y}`));
3554
+ if (positions.size > 2) return null;
3555
+ return `${elements.length} elements with no explicit layout \u2014 chalk_arrange(op: "layout-layered") will place them`;
3556
+ }
3198
3557
 
3199
3558
  // src/app/usecases/write/annotate-elements.ts
3200
3559
  var NOTE_GAP = 24;
3560
+ var NOTE_FONT_SIZE = 16;
3561
+ var NOTE_SEARCH_STEP = 40;
3562
+ var NOTE_SEARCH_LIMIT = 24;
3563
+ function clearSpotBelow(scene, preferred, size) {
3564
+ for (let i = 0; i <= NOTE_SEARCH_LIMIT; i++) {
3565
+ const candidate = {
3566
+ x: preferred.x,
3567
+ y: preferred.y + i * NOTE_SEARCH_STEP,
3568
+ width: size.width,
3569
+ height: size.height
3570
+ };
3571
+ if (!wouldCollide(candidate, scene)) return { x: candidate.x, y: candidate.y };
3572
+ }
3573
+ return preferred;
3574
+ }
3201
3575
  function annotateElements(deps) {
3202
3576
  return async (input) => {
3203
3577
  const scene = deps.write.resolve(input.sceneId);
@@ -3212,14 +3586,20 @@ function annotateElements(deps) {
3212
3586
  if (anchors.length === 0) return fail(emptySelection("chalk_annotate"));
3213
3587
  const left = Math.min(...anchors.map((element) => element.x));
3214
3588
  const bottom = Math.max(...anchors.map((element) => element.y + element.height));
3589
+ const wrapped = noteWrap(input.text, NOTE_FONT_SIZE, void 0);
3590
+ const size = {
3591
+ width: wrapped.width ?? measureLine(wrapped.text, NOTE_FONT_SIZE),
3592
+ height: wrapped.text.split("\n").length * NOTE_FONT_SIZE * LINE_HEIGHT
3593
+ };
3594
+ const spot = clearSpotBelow(scene.value.elements, { x: left, y: bottom + NOTE_GAP }, size);
3215
3595
  const skeleton = {
3216
3596
  type: "text",
3217
3597
  id: deps.idGen.elementId(),
3218
- x: left,
3219
- y: bottom + NOTE_GAP,
3220
- text: input.text,
3598
+ x: spot.x,
3599
+ y: spot.y,
3600
+ ...wrapped,
3221
3601
  // A size below the labels, so a note reads as commentary rather than as content.
3222
- fontSize: 16,
3602
+ fontSize: NOTE_FONT_SIZE,
3223
3603
  fontFamily: HOUSE.fontFamily,
3224
3604
  strokeColor: styleForRole("external").strokeColor,
3225
3605
  customData: {
@@ -3273,6 +3653,74 @@ function focusElements(deps) {
3273
3653
  return ok({ sceneId: scene.value.id, focused, broadcastTo });
3274
3654
  };
3275
3655
  }
3656
+
3657
+ // src/domain/scene/frames.ts
3658
+ function boundsAround(children) {
3659
+ const pad2 = HOUSE.sizes.framePad;
3660
+ const left = Math.min(...children.map((c) => c.x));
3661
+ const top = Math.min(...children.map((c) => c.y));
3662
+ const right = Math.max(...children.map((c) => c.x + c.width));
3663
+ const bottom = Math.max(...children.map((c) => c.y + c.height));
3664
+ return {
3665
+ x: left - pad2,
3666
+ y: top - pad2,
3667
+ width: right - left + pad2 * 2,
3668
+ // Room at the top for the frame's own name, which renders above its border.
3669
+ height: bottom - top + pad2 * 2
3670
+ };
3671
+ }
3672
+ function childrenOf(scene, frame) {
3673
+ const id = frameIdOf(frame.id);
3674
+ return scene.filter(
3675
+ (element) => !element.isDeleted && element.id !== frame.id && element.frameId === id
3676
+ );
3677
+ }
3678
+ function releaseCrossingArrows(scene, frame) {
3679
+ const id = frameIdOf(frame.id);
3680
+ const inside = new Set(childrenOf(scene, frame).map((element) => element.id));
3681
+ const byId = new Map(scene.map((element) => [element.id, element]));
3682
+ return childrenOf(scene, frame).filter((element) => element.type === "arrow" || element.type === "line").filter((arrow) => {
3683
+ if (arrow.type !== "arrow") return true;
3684
+ const from = arrow.startBinding?.elementId;
3685
+ const to = arrow.endBinding?.elementId;
3686
+ if (from === void 0 || to === void 0) return true;
3687
+ return !(inside.has(from) && inside.has(to));
3688
+ }).filter((arrow) => byId.get(arrow.id)?.frameId === id).map((arrow) => ({ ...arrow, frameId: null }));
3689
+ }
3690
+ function withFrameChildren(scene, deltas) {
3691
+ const frames = scene.filter(
3692
+ (element) => element.type === "frame" && !element.isDeleted && deltas.has(element.id)
3693
+ );
3694
+ if (frames.length === 0) return deltas;
3695
+ const widened = new Map(deltas);
3696
+ for (const frame of frames) {
3697
+ const delta = deltas.get(frame.id);
3698
+ if (delta === void 0) continue;
3699
+ for (const child of childrenOf(scene, frame)) {
3700
+ if (widened.has(child.id)) continue;
3701
+ widened.set(child.id, delta);
3702
+ }
3703
+ }
3704
+ return widened;
3705
+ }
3706
+ function refitFrames(scene, moved) {
3707
+ if (moved.size === 0) return [];
3708
+ const current = scene.map((element) => moved.get(element.id) ?? element);
3709
+ const refitted = [];
3710
+ for (const element of current) {
3711
+ if (element.type !== "frame" || element.isDeleted) continue;
3712
+ const crossing = new Set(releaseCrossingArrows(current, element).map((a) => a.id));
3713
+ const children = childrenOf(current, element).filter((c) => !crossing.has(c.id));
3714
+ if (children.length === 0) continue;
3715
+ if (!children.some((child) => moved.has(child.id))) continue;
3716
+ const box = boundsAround(children);
3717
+ if (element.x === box.x && element.y === box.y && element.width === box.width && element.height === box.height) {
3718
+ continue;
3719
+ }
3720
+ refitted.push({ ...element, ...box });
3721
+ }
3722
+ return refitted;
3723
+ }
3276
3724
  function highestIndex(elements) {
3277
3725
  let highest = null;
3278
3726
  for (const element of elements) {
@@ -3295,6 +3743,176 @@ function betweenIndices(lower, upper, count2) {
3295
3743
  return generateNKeysBetween(lower, upper, count2).map((key) => Index.parse(key));
3296
3744
  }
3297
3745
 
3746
+ // src/domain/scene/route.ts
3747
+ var ROUTE_CLEARANCE = 30;
3748
+ var TURN_PENALTY = 60;
3749
+ var MAX_GRID = 64;
3750
+ var EPSILON = 1e-3;
3751
+ function segmentHitsRect(a, b, rect) {
3752
+ let t0 = 0;
3753
+ let t1 = 1;
3754
+ const dx = b[0] - a[0];
3755
+ const dy = b[1] - a[1];
3756
+ const left = rect.x + EPSILON;
3757
+ const right = rect.x + rect.width - EPSILON;
3758
+ const top = rect.y + EPSILON;
3759
+ const bottom = rect.y + rect.height - EPSILON;
3760
+ const edges = [
3761
+ [-dx, a[0] - left],
3762
+ [dx, right - a[0]],
3763
+ [-dy, a[1] - top],
3764
+ [dy, bottom - a[1]]
3765
+ ];
3766
+ for (const edge of edges) {
3767
+ const den = edge[0];
3768
+ const num = edge[1];
3769
+ if (den === 0) {
3770
+ if (num < 0) return false;
3771
+ continue;
3772
+ }
3773
+ const t = num / den;
3774
+ if (den < 0) {
3775
+ if (t > t1) return false;
3776
+ if (t > t0) t0 = t;
3777
+ } else {
3778
+ if (t < t0) return false;
3779
+ if (t < t1) t1 = t;
3780
+ }
3781
+ }
3782
+ return t1 - t0 > EPSILON;
3783
+ }
3784
+ function routeHitsAny(points, obstacles) {
3785
+ for (let i = 1; i < points.length; i++) {
3786
+ const a = points[i - 1];
3787
+ const b = points[i];
3788
+ if (a === void 0 || b === void 0) continue;
3789
+ for (const rect of obstacles) if (segmentHitsRect(a, b, rect)) return true;
3790
+ }
3791
+ return false;
3792
+ }
3793
+ function axis(ends, sides, clearance) {
3794
+ const values = new Set(ends);
3795
+ for (const [low, high] of sides) {
3796
+ values.add(low - clearance);
3797
+ values.add(high + clearance);
3798
+ }
3799
+ const sorted = [...values].sort((a, b) => a - b);
3800
+ if (sorted.length <= MAX_GRID) return sorted;
3801
+ const step = sorted.length / MAX_GRID;
3802
+ const kept = new Set(ends);
3803
+ for (let i = 0; i < MAX_GRID; i++) {
3804
+ const value = sorted[Math.floor(i * step)];
3805
+ if (value !== void 0) kept.add(value);
3806
+ }
3807
+ return [...kept].sort((a, b) => a - b);
3808
+ }
3809
+ function routeAround(start, end, obstacles, clearance = ROUTE_CLEARANCE) {
3810
+ if (obstacles.length === 0) return null;
3811
+ const xs = axis(
3812
+ [start[0], end[0]],
3813
+ obstacles.map((r) => [r.x, r.x + r.width]),
3814
+ clearance
3815
+ );
3816
+ const ys = axis(
3817
+ [start[1], end[1]],
3818
+ obstacles.map((r) => [r.y, r.y + r.height]),
3819
+ clearance
3820
+ );
3821
+ const xi = xs.indexOf(start[0]);
3822
+ const yi = ys.indexOf(start[1]);
3823
+ const xg = xs.indexOf(end[0]);
3824
+ const yg = ys.indexOf(end[1]);
3825
+ if (xi < 0 || yi < 0 || xg < 0 || yg < 0) return null;
3826
+ const width = xs.length;
3827
+ const index = (cx, cy) => cy * width + cx;
3828
+ const goal = index(xg, yg);
3829
+ const at = (cx, cy) => {
3830
+ const x = xs[cx];
3831
+ const y = ys[cy];
3832
+ return [x ?? 0, y ?? 0];
3833
+ };
3834
+ const passable = (a, b) => {
3835
+ for (const rect of obstacles) if (segmentHitsRect(a, b, rect)) return false;
3836
+ return true;
3837
+ };
3838
+ const total = width * ys.length;
3839
+ const best = new Float64Array(total).fill(Number.POSITIVE_INFINITY);
3840
+ const cameFrom = new Int32Array(total).fill(-1);
3841
+ const arrived = new Uint8Array(total);
3842
+ const done = new Uint8Array(total);
3843
+ const startAt = index(xi, yi);
3844
+ best[startAt] = 0;
3845
+ const heuristic = (cx, cy) => Math.abs((xs[cx] ?? 0) - end[0]) + Math.abs((ys[cy] ?? 0) - end[1]);
3846
+ for (; ; ) {
3847
+ let node = -1;
3848
+ let cheapest = Number.POSITIVE_INFINITY;
3849
+ for (let i = 0; i < total; i++) {
3850
+ if (done[i] === 1) continue;
3851
+ const score = best[i];
3852
+ if (score === void 0 || !Number.isFinite(score)) continue;
3853
+ const estimate = score + heuristic(i % width, Math.floor(i / width));
3854
+ if (estimate < cheapest) {
3855
+ cheapest = estimate;
3856
+ node = i;
3857
+ }
3858
+ }
3859
+ if (node < 0) return null;
3860
+ if (node === goal) break;
3861
+ done[node] = 1;
3862
+ const cx = node % width;
3863
+ const cy = Math.floor(node / width);
3864
+ const here = at(cx, cy);
3865
+ const cost = best[node] ?? 0;
3866
+ const steps = [
3867
+ [cx - 1, cy, 1],
3868
+ [cx + 1, cy, 1],
3869
+ [cx, cy - 1, 2],
3870
+ [cx, cy + 1, 2]
3871
+ ];
3872
+ for (const [nx, ny, axisOf] of steps) {
3873
+ if (nx < 0 || ny < 0 || nx >= width || ny >= ys.length) continue;
3874
+ const next = index(nx, ny);
3875
+ if (done[next] === 1) continue;
3876
+ const there = at(nx, ny);
3877
+ if (!passable(here, there)) continue;
3878
+ const length = Math.abs(there[0] - here[0]) + Math.abs(there[1] - here[1]);
3879
+ const turn = arrived[node] !== 0 && arrived[node] !== axisOf ? TURN_PENALTY : 0;
3880
+ const candidate = cost + length + turn;
3881
+ const known = best[next];
3882
+ if (known === void 0 || candidate < known) {
3883
+ best[next] = candidate;
3884
+ cameFrom[next] = node;
3885
+ arrived[next] = axisOf;
3886
+ }
3887
+ }
3888
+ }
3889
+ const path = [];
3890
+ for (let node = goal; node >= 0; node = cameFrom[node] ?? -1) {
3891
+ path.push(at(node % width, Math.floor(node / width)));
3892
+ if (node === startAt) break;
3893
+ }
3894
+ path.reverse();
3895
+ return simplify(path);
3896
+ }
3897
+ function simplify(points) {
3898
+ if (points.length < 3) return points;
3899
+ const out = [];
3900
+ for (let i = 0; i < points.length; i++) {
3901
+ const previous = out[out.length - 1];
3902
+ const current = points[i];
3903
+ const next = points[i + 1];
3904
+ if (current === void 0) continue;
3905
+ if (previous === void 0 || next === void 0) {
3906
+ out.push(current);
3907
+ continue;
3908
+ }
3909
+ const straightX = previous[0] === current[0] && current[0] === next[0];
3910
+ const straightY = previous[1] === current[1] && current[1] === next[1];
3911
+ if (!straightX && !straightY) out.push(current);
3912
+ }
3913
+ return out;
3914
+ }
3915
+
3298
3916
  // src/domain/scene/arrange.ts
3299
3917
  var boxOf = (element) => ({
3300
3918
  x: element.x,
@@ -3337,7 +3955,39 @@ function edgePoint(from, to, gap) {
3337
3955
  const t = scale + push;
3338
3956
  return [cx + dx * t, cy + dy * t];
3339
3957
  }
3340
- function redrawOne(current, labels, arrow) {
3958
+ var ROUTABLE_OBSTACLE = /* @__PURE__ */ new Set(["rectangle", "diamond", "ellipse", "image", "embeddable"]);
3959
+ function obstaclesFor(current, fromId, toId) {
3960
+ const out = [];
3961
+ for (const element of current.values()) {
3962
+ if (element.id === fromId || element.id === toId) continue;
3963
+ if (element.isDeleted || !ROUTABLE_OBSTACLE.has(element.type)) continue;
3964
+ out.push(boxOf(element));
3965
+ }
3966
+ return out;
3967
+ }
3968
+ var NO_FAN = { start: 0, end: 0 };
3969
+ function stubFrom(box, at) {
3970
+ const [cx, cy] = centre(box);
3971
+ const horizontal = at[0] <= box.x || at[0] >= box.x + box.width;
3972
+ if (horizontal) return [at[0] + (at[0] < cx ? -ROUTE_CLEARANCE : ROUTE_CLEARANCE), at[1]];
3973
+ return [at[0], at[1] + (at[1] < cy ? -ROUTE_CLEARANCE : ROUTE_CLEARANCE)];
3974
+ }
3975
+ function anchorOn(box, adjacent, gap) {
3976
+ const [cx, cy] = centre(box);
3977
+ const dx = adjacent[0] - cx;
3978
+ const dy = adjacent[1] - cy;
3979
+ if (dx === 0 && dy === 0) return [cx, cy];
3980
+ const toSide = Math.min(
3981
+ dx === 0 ? Number.POSITIVE_INFINITY : box.width / 2 / Math.abs(dx),
3982
+ dy === 0 ? Number.POSITIVE_INFINITY : box.height / 2 / Math.abs(dy)
3983
+ );
3984
+ const bx = cx + dx * toSide;
3985
+ const by = cy + dy * toSide;
3986
+ const reach = Math.hypot(dx * toSide, dy * toSide);
3987
+ if (reach === 0) return [bx, by];
3988
+ return [bx + dx * toSide * gap / reach, by + dy * toSide * gap / reach];
3989
+ }
3990
+ function redrawOne(current, labels, arrow, lane) {
3341
3991
  if (arrow.type !== "arrow") return [];
3342
3992
  const fromId = startOf(arrow);
3343
3993
  const toId = endOf(arrow);
@@ -3347,40 +3997,163 @@ function redrawOne(current, labels, arrow) {
3347
3997
  if (from === void 0 || to === void 0) return [];
3348
3998
  const startGap = arrow.startBinding?.gap ?? 4;
3349
3999
  const endGap = arrow.endBinding?.gap ?? 4;
3350
- const [sx, sy] = edgePoint(boxOf(from), boxOf(to), startGap);
3351
- const [ex, ey] = edgePoint(boxOf(to), boxOf(from), endGap);
3352
- const points = [point(0, 0), point(ex - sx, ey - sy)];
4000
+ const elbowed = arrow.elbowed === true;
4001
+ const [sx, sy, ex, ey] = elbowed ? facePoints(boxOf(from), boxOf(to), startGap, endGap, lane) : [
4002
+ ...edgePoint(boxOf(from), boxOf(to), startGap),
4003
+ ...edgePoint(boxOf(to), boxOf(from), endGap)
4004
+ ];
4005
+ const direct = elbowed ? elbowRoute(ex - sx, ey - sy) : [point(0, 0), point(ex - sx, ey - sy)];
4006
+ const obstacles = obstaclesFor(current, fromId, toId);
4007
+ const absolute = direct.map(([px2, py]) => [sx + px2, sy + py]);
4008
+ const detoured = routeHitsAny(absolute, obstacles);
4009
+ const [fsx, fsy, fex, fey] = detoured ? facePoints(boxOf(from), boxOf(to), startGap, endGap, NO_FAN) : [sx, sy, ex, ey];
4010
+ const stubStart = stubFrom(boxOf(from), [fsx, fsy]);
4011
+ const stubEnd = stubFrom(boxOf(to), [fex, fey]);
4012
+ const found = detoured ? routeAround(stubStart, stubEnd, obstacles) : null;
4013
+ const route = found === null ? null : simplify([[fsx, fsy], ...found, [fex, fey]]);
4014
+ const drawn = route ?? direct.map(([px2, py]) => [sx + px2, sy + py]);
4015
+ const settled = [...drawn];
4016
+ const secondFromStart = settled[1];
4017
+ const secondFromEnd = settled[settled.length - 2];
4018
+ if (secondFromStart !== void 0) {
4019
+ settled[0] = anchorOn(boxOf(from), secondFromStart, startGap);
4020
+ }
4021
+ if (secondFromEnd !== void 0) {
4022
+ settled[settled.length - 1] = anchorOn(boxOf(to), secondFromEnd, endGap);
4023
+ }
4024
+ const first = settled[0] ?? [sx, sy];
4025
+ const origin = [first[0], first[1]];
4026
+ const points = settled.map(([ax, ay]) => point(ax - origin[0], ay - origin[1]));
4027
+ const pxs = points.map(([px2]) => px2);
4028
+ const pys = points.map(([, py]) => py);
4029
+ const spans = {
4030
+ w: Math.max(...pxs) - Math.min(...pxs),
4031
+ h: Math.max(...pys) - Math.min(...pys)
4032
+ };
3353
4033
  const redrawn = {
3354
4034
  ...arrow,
3355
- x: sx,
3356
- y: sy,
3357
- width: Math.abs(ex - sx),
3358
- height: Math.abs(ey - sy),
4035
+ // A detour is an orthogonal route, so it is drawn as one.
4036
+ //
4037
+ // This is not cosmetic. Excalidraw owns the endpoints of a *bound* arrow at paint
4038
+ // time: with `focus: 0` it re-derives where the line meets each box from the segment
4039
+ // adjacent to it, and a detour changes exactly that segment. The stored endpoints are
4040
+ // right — all twenty-nine measured on the boundary of the box they name — and the
4041
+ // canvas still drew several of them leaving from nowhere, because the renderer had
4042
+ // recomputed them from a first segment that now pointed the wrong way. An elbow arrow
4043
+ // is anchored differently and keeps the route it was given.
4044
+ //
4045
+ // The second reason is what it looks like. A right-angled detour rendered with
4046
+ // `roundness` comes out as a long S sweeping across the diagram — which is what the
4047
+ // bend at the bottom of the sample was. Square corners are what an orthogonal route
4048
+ // actually means.
4049
+ ...route === null ? {} : { elbowed: true, roundness: null },
4050
+ x: origin[0],
4051
+ y: origin[1],
4052
+ width: spans.w,
4053
+ height: spans.h,
3359
4054
  points,
3360
4055
  lastCommittedPoint: null
3361
4056
  };
3362
4057
  const label = labels.get(arrow.id);
3363
4058
  if (label === void 0) return [redrawn];
3364
- return [
3365
- redrawn,
3366
- {
3367
- ...label,
3368
- x: (sx + ex) / 2 - label.width / 2,
3369
- y: (sy + ey) / 2 - label.height / 2
3370
- }
3371
- ];
4059
+ const mid = routeMidpoint({ x: origin[0], y: origin[1], points });
4060
+ const at = { x: mid.x - label.width / 2, y: mid.y - label.height / 2 };
4061
+ return [redrawn, { ...label, ...at }];
4062
+ }
4063
+ function faceBetween(from, to) {
4064
+ const [fx, fy] = centre(from);
4065
+ const [tx, ty] = centre(to);
4066
+ const dx = tx - fx;
4067
+ const dy = ty - fy;
4068
+ if (Math.abs(dx) >= Math.abs(dy)) return dx >= 0 ? "right" : "left";
4069
+ return dy >= 0 ? "bottom" : "top";
4070
+ }
4071
+ var LANE_SPREAD = 44;
4072
+ var LANE_INSET = 10;
4073
+ function laneOffsets(arrows, current) {
4074
+ const groups = /* @__PURE__ */ new Map();
4075
+ const add = (nodeId, face, node, slot) => {
4076
+ const key = `${nodeId}|${face}`;
4077
+ const bucket = groups.get(key);
4078
+ if (bucket === void 0) groups.set(key, { face, node, slots: [slot] });
4079
+ else bucket.slots.push(slot);
4080
+ };
4081
+ const across = (face, box) => face === "right" || face === "left" ? centre(box)[1] : centre(box)[0];
4082
+ for (const arrow of arrows) {
4083
+ if (arrow.type !== "arrow" || arrow.elbowed !== true) continue;
4084
+ const fromId = startOf(arrow);
4085
+ const toId = endOf(arrow);
4086
+ if (fromId === null || toId === null) continue;
4087
+ const from = current.get(fromId);
4088
+ const to = current.get(toId);
4089
+ if (from === void 0 || to === void 0) continue;
4090
+ const outFace = faceBetween(boxOf(from), boxOf(to));
4091
+ const inFace = faceBetween(boxOf(to), boxOf(from));
4092
+ add(fromId, outFace, boxOf(from), {
4093
+ arrow: arrow.id,
4094
+ end: "start",
4095
+ along: across(outFace, boxOf(to))
4096
+ });
4097
+ add(toId, inFace, boxOf(to), {
4098
+ arrow: arrow.id,
4099
+ end: "end",
4100
+ along: across(inFace, boxOf(from))
4101
+ });
4102
+ }
4103
+ const offsets = /* @__PURE__ */ new Map();
4104
+ for (const { face, node, slots } of groups.values()) {
4105
+ if (slots.length < 2) continue;
4106
+ slots.sort((a, b) => a.along - b.along || (a.arrow < b.arrow ? -1 : 1));
4107
+ const span = face === "right" || face === "left" ? node.height : node.width;
4108
+ const usable = Math.max(0, span - LANE_INSET * 2);
4109
+ const spread = Math.min(LANE_SPREAD, usable / Math.max(1, slots.length - 1));
4110
+ slots.forEach((slot, i) => {
4111
+ const shift = (i - (slots.length - 1) / 2) * spread;
4112
+ const so_far = offsets.get(slot.arrow) ?? { start: 0, end: 0 };
4113
+ offsets.set(slot.arrow, { ...so_far, [slot.end]: shift });
4114
+ });
4115
+ }
4116
+ return offsets;
4117
+ }
4118
+ function facePoints(from, to, startGap, endGap, lane) {
4119
+ const [fx, fy] = centre(from);
4120
+ const [tx, ty] = centre(to);
4121
+ const dx = tx - fx;
4122
+ const dy = ty - fy;
4123
+ if (Math.abs(dx) >= Math.abs(dy)) {
4124
+ const sx = dx >= 0 ? from.x + from.width + startGap : from.x - startGap;
4125
+ const ex = dx >= 0 ? to.x - endGap : to.x + to.width + endGap;
4126
+ return [sx, fy + lane.start, ex, ty + lane.end];
4127
+ }
4128
+ const sy = dy >= 0 ? from.y + from.height + startGap : from.y - startGap;
4129
+ const ey = dy >= 0 ? to.y - endGap : to.y + to.height + endGap;
4130
+ return [fx + lane.start, sy, tx + lane.end, ey];
4131
+ }
4132
+ function elbowRoute(dx, dy) {
4133
+ const ALIGNED = 1;
4134
+ if (Math.abs(dy) <= ALIGNED || Math.abs(dx) <= ALIGNED) {
4135
+ return [point(0, 0), point(dx, dy)];
4136
+ }
4137
+ if (Math.abs(dx) >= Math.abs(dy)) {
4138
+ const mid2 = dx / 2;
4139
+ return [point(0, 0), point(mid2, 0), point(mid2, dy), point(dx, dy)];
4140
+ }
4141
+ const mid = dy / 2;
4142
+ return [point(0, 0), point(0, mid), point(dx, mid), point(dx, dy)];
3372
4143
  }
3373
4144
  function redrawArrowsTouching(scene, moved) {
3374
4145
  const current = new Map(scene.map((element) => [element.id, moved.get(element.id) ?? element]));
3375
4146
  const labels = labelsByContainer([...current.values()]);
3376
4147
  const redrawn = [];
4148
+ const lanes = laneOffsets([...current.values()], current);
4149
+ const NO_LANE = { start: 0, end: 0 };
3377
4150
  for (const element of current.values()) {
3378
4151
  if (element.type !== "arrow") continue;
3379
4152
  const fromId = startOf(element);
3380
4153
  const toId = endOf(element);
3381
4154
  if (fromId === null || toId === null) continue;
3382
4155
  if (!moved.has(fromId) && !moved.has(toId)) continue;
3383
- redrawn.push(...redrawOne(current, labels, element));
4156
+ redrawn.push(...redrawOne(current, labels, element, lanes.get(element.id) ?? NO_LANE));
3384
4157
  }
3385
4158
  return redrawn;
3386
4159
  }
@@ -3388,13 +4161,16 @@ function redrawArrows(scene, ids) {
3388
4161
  const current = new Map(scene.map((element) => [element.id, element]));
3389
4162
  const labels = labelsByContainer(scene);
3390
4163
  const wanted = new Set(ids);
3391
- return [...current.values()].filter((element) => wanted.has(element.id)).flatMap((element) => redrawOne(current, labels, element));
4164
+ const lanes = laneOffsets([...current.values()], current);
4165
+ const NO_LANE = { start: 0, end: 0 };
4166
+ return [...current.values()].filter((element) => wanted.has(element.id)).flatMap((element) => redrawOne(current, labels, element, lanes.get(element.id) ?? NO_LANE));
3392
4167
  }
3393
4168
  function translate(scene, deltas) {
4169
+ const widened = withAnchoredElements(scene, withFrameChildren(scene, deltas));
3394
4170
  const labels = labelsByContainer(scene);
3395
4171
  const moved = /* @__PURE__ */ new Map();
3396
4172
  for (const element of scene) {
3397
- const delta = deltas.get(element.id);
4173
+ const delta = widened.get(element.id);
3398
4174
  if (delta === void 0) continue;
3399
4175
  const [dx, dy] = delta;
3400
4176
  if (dx === 0 && dy === 0) continue;
@@ -3404,7 +4180,10 @@ function translate(scene, deltas) {
3404
4180
  moved.set(label.id, { ...label, x: label.x + dx, y: label.y + dy });
3405
4181
  }
3406
4182
  }
3407
- return [...moved.values(), ...redrawArrowsTouching(scene, moved)];
4183
+ const changed = new Map(moved);
4184
+ for (const element of redrawArrowsTouching(scene, moved)) changed.set(element.id, element);
4185
+ for (const element of refitFrames(scene, moved)) changed.set(element.id, element);
4186
+ return [...changed.values()];
3408
4187
  }
3409
4188
  var alignments = {
3410
4189
  "align-left": (box, bounds) => [bounds.x - box.x, 0],
@@ -3416,14 +4195,14 @@ var alignments = {
3416
4195
  "align-center-h": (box, bounds) => [0, bounds.y + bounds.height / 2 - (box.y + box.height / 2)],
3417
4196
  "align-center-v": (box, bounds) => [bounds.x + bounds.width / 2 - (box.x + box.width / 2), 0]
3418
4197
  };
3419
- function distribute(boxes, spacing, axis) {
3420
- const size = axis === "x" ? "width" : "height";
3421
- const order = boxes.map((box, i) => ({ box, i })).sort((a, b) => a.box[axis] - b.box[axis]);
4198
+ function distribute(boxes, spacing, axis2) {
4199
+ const size = axis2 === "x" ? "width" : "height";
4200
+ const order = boxes.map((box, i) => ({ box, i })).sort((a, b) => a.box[axis2] - b.box[axis2]);
3422
4201
  const deltas = boxes.map(() => [0, 0]);
3423
- let cursor = order[0]?.box[axis] ?? 0;
4202
+ let cursor = order[0]?.box[axis2] ?? 0;
3424
4203
  for (const { box, i } of order) {
3425
- const delta = cursor - box[axis];
3426
- deltas[i] = axis === "x" ? [delta, 0] : [0, delta];
4204
+ const delta = cursor - box[axis2];
4205
+ deltas[i] = axis2 === "x" ? [delta, 0] : [0, delta];
3427
4206
  cursor += box[size] + spacing;
3428
4207
  }
3429
4208
  return deltas;
@@ -3439,10 +4218,10 @@ function grid(boxes, options) {
3439
4218
  return [anchor.x + column * cellW - box.x, anchor.y + rowIndex * cellH - box.y];
3440
4219
  });
3441
4220
  }
3442
- function flip(boxes, axis) {
4221
+ function flip(boxes, axis2) {
3443
4222
  const bounds = boundsOfAll(boxes);
3444
4223
  return boxes.map((box) => {
3445
- if (axis === "x") {
4224
+ if (axis2 === "x") {
3446
4225
  const mirrored2 = bounds.x + bounds.width - (box.x - bounds.x) - box.width;
3447
4226
  return [mirrored2 - box.x, 0];
3448
4227
  }
@@ -3503,6 +4282,12 @@ function arrangeGeometry(input) {
3503
4282
  if (input.op === "layout-layered") {
3504
4283
  return ok({ moved: layoutLayered(input.scene, targets, input.spacing) });
3505
4284
  }
4285
+ if (input.op === "layout-radial") {
4286
+ return ok({ moved: layoutRadial(input.scene, targets, input.spacing) });
4287
+ }
4288
+ if (input.op === "layout-tree") {
4289
+ return ok({ moved: layoutTree(input.scene, targets, input.spacing) });
4290
+ }
3506
4291
  const boxes = targets.map(boxOf);
3507
4292
  const spacing = input.spacing ?? HOUSE.spacing.h;
3508
4293
  const columns = input.columns ?? Math.ceil(Math.sqrt(targets.length));
@@ -3534,6 +4319,44 @@ function arrangeGeometry(input) {
3534
4319
  });
3535
4320
  return ok({ moved: translate(input.scene, byDelta) });
3536
4321
  }
4322
+ function backEdgeIndices(nodeIds, edges) {
4323
+ const outgoing = /* @__PURE__ */ new Map();
4324
+ for (const id of nodeIds) outgoing.set(id, []);
4325
+ edges.forEach((edge, index) => {
4326
+ outgoing.get(edge.from)?.push(index);
4327
+ });
4328
+ const ON_STACK = 1;
4329
+ const DONE = 2;
4330
+ const state = /* @__PURE__ */ new Map();
4331
+ const back = /* @__PURE__ */ new Set();
4332
+ for (const root of [...nodeIds].sort()) {
4333
+ if (state.has(root)) continue;
4334
+ state.set(root, ON_STACK);
4335
+ const stack = [{ node: root, cursor: 0 }];
4336
+ while (stack.length > 0) {
4337
+ const frame = stack[stack.length - 1];
4338
+ if (frame === void 0) break;
4339
+ const fanOut = outgoing.get(frame.node) ?? [];
4340
+ if (frame.cursor >= fanOut.length) {
4341
+ state.set(frame.node, DONE);
4342
+ stack.pop();
4343
+ continue;
4344
+ }
4345
+ const index = fanOut[frame.cursor];
4346
+ frame.cursor += 1;
4347
+ if (index === void 0) continue;
4348
+ const head = edges[index]?.to;
4349
+ if (head === void 0) continue;
4350
+ const seen = state.get(head);
4351
+ if (seen === ON_STACK) back.add(index);
4352
+ else if (seen === void 0) {
4353
+ state.set(head, ON_STACK);
4354
+ stack.push({ node: head, cursor: 0 });
4355
+ }
4356
+ }
4357
+ }
4358
+ return back;
4359
+ }
3537
4360
  function layoutLayered(scene, targets, spacing) {
3538
4361
  const inSet = new Set(targets.map((element) => element.id));
3539
4362
  const edges = [];
@@ -3545,22 +4368,20 @@ function layoutLayered(scene, targets, spacing) {
3545
4368
  if (!inSet.has(from) || !inSet.has(to) || from === to) continue;
3546
4369
  edges.push({ from, to });
3547
4370
  }
3548
- const nodes = targets.filter((element) => !isLinear(element));
4371
+ const nodes = targets.filter((element) => !isLinear(element) && !isAnchored(element));
3549
4372
  if (nodes.length === 0) return [];
4373
+ const back = backEdgeIndices(
4374
+ nodes.map((node) => node.id),
4375
+ edges
4376
+ );
4377
+ const forward = edges.filter((_, index) => !back.has(index));
3550
4378
  const incoming = /* @__PURE__ */ new Map();
3551
- const outgoing = /* @__PURE__ */ new Map();
3552
- for (const node of nodes) {
3553
- incoming.set(node.id, []);
3554
- outgoing.set(node.id, []);
3555
- }
3556
- for (const edge of edges) {
3557
- incoming.get(edge.to)?.push(edge.from);
3558
- outgoing.get(edge.from)?.push(edge.to);
3559
- }
4379
+ for (const node of nodes) incoming.set(node.id, []);
4380
+ for (const edge of forward) incoming.get(edge.to)?.push(edge.from);
3560
4381
  const rank = new Map(nodes.map((node) => [node.id, 0]));
3561
4382
  for (let pass = 0; pass < nodes.length; pass++) {
3562
4383
  let settled = true;
3563
- for (const edge of edges) {
4384
+ for (const edge of forward) {
3564
4385
  const from = rank.get(edge.from) ?? 0;
3565
4386
  const to = rank.get(edge.to) ?? 0;
3566
4387
  if (to < from + 1) {
@@ -3570,60 +4391,361 @@ function layoutLayered(scene, targets, spacing) {
3570
4391
  }
3571
4392
  if (settled) break;
3572
4393
  }
3573
- const ranks = /* @__PURE__ */ new Map();
4394
+ const LANE_WIDTH = 0;
4395
+ const LANE_HEIGHT = 48;
4396
+ const slots = /* @__PURE__ */ new Map();
4397
+ const push = (r, slot) => {
4398
+ const bucket = slots.get(r);
4399
+ if (bucket === void 0) slots.set(r, [slot]);
4400
+ else bucket.push(slot);
4401
+ };
3574
4402
  for (const node of nodes) {
3575
- const r = rank.get(node.id) ?? 0;
3576
- const bucket = ranks.get(r);
3577
- if (bucket === void 0) ranks.set(r, [node]);
3578
- else bucket.push(node);
4403
+ push(rank.get(node.id) ?? 0, {
4404
+ key: node.id,
4405
+ box: boxOf(node),
4406
+ parents: incoming.get(node.id) ?? [],
4407
+ node
4408
+ });
3579
4409
  }
3580
- const position = /* @__PURE__ */ new Map();
3581
- const rankNumbers = [...ranks.keys()].sort((a, b) => a - b);
4410
+ for (const edge of edges) {
4411
+ const from = rank.get(edge.from);
4412
+ const to = rank.get(edge.to);
4413
+ if (from === void 0 || to === void 0) continue;
4414
+ let previous = edge.from;
4415
+ for (let r = Math.min(from, to) + 1; r < Math.max(from, to); r++) {
4416
+ const key = `lane:${edge.from}>${edge.to}@${r}`;
4417
+ push(r, {
4418
+ key,
4419
+ box: { x: 0, y: 0, width: LANE_WIDTH, height: LANE_HEIGHT },
4420
+ parents: [previous]
4421
+ });
4422
+ previous = key;
4423
+ }
4424
+ }
4425
+ const rankNumbers = [...slots.keys()].sort((a, b) => a - b);
4426
+ const childrenOf2 = /* @__PURE__ */ new Map();
3582
4427
  for (const r of rankNumbers) {
3583
- const bucket = ranks.get(r) ?? [];
3584
- const scored = bucket.map((node) => {
3585
- const parents = (incoming.get(node.id) ?? []).map((id) => position.get(id)).filter((p) => p !== void 0);
3586
- const barycentre = parents.length === 0 ? Number.POSITIVE_INFINITY : parents.reduce((sum, p) => sum + p, 0) / parents.length;
3587
- return { node, barycentre };
3588
- });
3589
- scored.sort(
3590
- (a, b) => a.barycentre - b.barycentre || (a.node.id < b.node.id ? -1 : a.node.id > b.node.id ? 1 : 0)
3591
- );
3592
- scored.forEach(({ node }, i) => {
3593
- position.set(node.id, i);
3594
- });
3595
- ranks.set(
3596
- r,
3597
- scored.map((entry) => entry.node)
3598
- );
4428
+ for (const slot of slots.get(r) ?? []) {
4429
+ for (const parent of slot.parents) {
4430
+ const kids = childrenOf2.get(parent);
4431
+ if (kids === void 0) childrenOf2.set(parent, [slot.key]);
4432
+ else kids.push(slot.key);
4433
+ }
4434
+ }
3599
4435
  }
3600
- const gapH = spacing ?? HOUSE.spacing.h;
4436
+ const indicesOf = (ordering) => {
4437
+ const at = /* @__PURE__ */ new Map();
4438
+ for (const bucket of ordering.values()) {
4439
+ bucket.forEach((slot, i) => {
4440
+ at.set(slot.key, i);
4441
+ });
4442
+ }
4443
+ return at;
4444
+ };
4445
+ const crossings = (ordering) => {
4446
+ const at = indicesOf(ordering);
4447
+ let total = 0;
4448
+ for (const r of rankNumbers) {
4449
+ const pairs = [];
4450
+ for (const slot of ordering.get(r) ?? []) {
4451
+ const below = at.get(slot.key);
4452
+ if (below === void 0) continue;
4453
+ for (const parent of slot.parents) {
4454
+ const above = at.get(parent);
4455
+ if (above !== void 0) pairs.push([above, below]);
4456
+ }
4457
+ }
4458
+ for (let i = 0; i < pairs.length; i++) {
4459
+ for (let j = i + 1; j < pairs.length; j++) {
4460
+ const a = pairs[i];
4461
+ const b = pairs[j];
4462
+ if (a === void 0 || b === void 0) continue;
4463
+ if ((a[0] - b[0]) * (a[1] - b[1]) < 0) total += 1;
4464
+ }
4465
+ }
4466
+ }
4467
+ return total;
4468
+ };
4469
+ const sweep = (ordering, direction) => {
4470
+ const at = indicesOf(ordering);
4471
+ const next = /* @__PURE__ */ new Map();
4472
+ const order = direction === "down" ? rankNumbers : [...rankNumbers].reverse();
4473
+ for (const r of order) {
4474
+ const scored = (ordering.get(r) ?? []).map((slot) => {
4475
+ const adjacent = direction === "down" ? slot.parents : childrenOf2.get(slot.key) ?? [];
4476
+ const known = adjacent.map((key) => at.get(key)).filter((index) => index !== void 0);
4477
+ return {
4478
+ slot,
4479
+ barycentre: known.length === 0 ? Number.POSITIVE_INFINITY : known.reduce((sum, index) => sum + index, 0) / known.length
4480
+ };
4481
+ });
4482
+ scored.sort(
4483
+ (a, b) => a.barycentre - b.barycentre || (a.slot.key < b.slot.key ? -1 : a.slot.key > b.slot.key ? 1 : 0)
4484
+ );
4485
+ const settled = scored.map((entry) => entry.slot);
4486
+ settled.forEach((slot, i) => {
4487
+ at.set(slot.key, i);
4488
+ });
4489
+ next.set(r, settled);
4490
+ }
4491
+ return next;
4492
+ };
4493
+ const SWEEPS = 4;
4494
+ let current = new Map(rankNumbers.map((r) => [r, [...slots.get(r) ?? []]]));
4495
+ let best = current;
4496
+ let fewest = Number.POSITIVE_INFINITY;
4497
+ for (let round = 0; round < SWEEPS; round++) {
4498
+ current = sweep(current, round % 2 === 0 ? "down" : "up");
4499
+ const score = crossings(current);
4500
+ if (score < fewest) {
4501
+ fewest = score;
4502
+ best = current;
4503
+ }
4504
+ }
4505
+ for (const r of rankNumbers) slots.set(r, [...best.get(r) ?? []]);
3601
4506
  const gapV = HOUSE.spacing.v;
4507
+ const labelWidths = labelsByContainer(scene);
4508
+ const captionRoom = (column) => {
4509
+ const floor = spacing ?? HOUSE.spacing.h;
4510
+ let widest = 0;
4511
+ for (const edge of edges) {
4512
+ const from = rank.get(edge.from);
4513
+ const to = rank.get(edge.to);
4514
+ if (from === void 0 || to === void 0) continue;
4515
+ const low = Math.min(from, to);
4516
+ const high = Math.max(from, to);
4517
+ if (column < low || column >= high) continue;
4518
+ const arrow = scene.find(
4519
+ (element) => element.type === "arrow" && startOf(element) === edge.from && endOf(element) === edge.to
4520
+ );
4521
+ const caption = arrow === void 0 ? void 0 : labelWidths.get(arrow.id);
4522
+ if (caption !== void 0) widest = Math.max(widest, caption.width);
4523
+ }
4524
+ return Math.max(floor, widest + HOUSE.sizes.labelPadX * 2);
4525
+ };
3602
4526
  const anchor = boundsOfAll(nodes.map(boxOf));
3603
4527
  const columnWidths = rankNumbers.map(
3604
- (r) => Math.max(...(ranks.get(r) ?? []).map((node) => node.width))
4528
+ (r) => Math.max(...(slots.get(r) ?? []).map((slot) => slot.box.width))
3605
4529
  );
3606
4530
  const tallest = Math.max(
3607
4531
  ...rankNumbers.map(
3608
- (r) => (ranks.get(r) ?? []).reduce((sum, node) => sum + node.height + gapV, -gapV)
4532
+ (r) => (slots.get(r) ?? []).reduce((sum, slot) => sum + slot.box.height + gapV, -gapV)
3609
4533
  )
3610
4534
  );
3611
4535
  const deltas = /* @__PURE__ */ new Map();
3612
4536
  let cursorX = anchor.x;
3613
4537
  rankNumbers.forEach((r, column) => {
3614
- const bucket = ranks.get(r) ?? [];
3615
- const columnHeight = bucket.reduce((sum, node) => sum + node.height + gapV, -gapV);
4538
+ const bucket = slots.get(r) ?? [];
4539
+ const columnHeight = bucket.reduce((sum, slot) => sum + slot.box.height + gapV, -gapV);
3616
4540
  let cursorY = anchor.y + (tallest - columnHeight) / 2;
3617
4541
  const width = columnWidths[column] ?? 0;
3618
- for (const node of bucket) {
3619
- const x = cursorX + (width - node.width) / 2;
3620
- deltas.set(node.id, [x - node.x, cursorY - node.y]);
3621
- cursorY += node.height + gapV;
4542
+ for (const slot of bucket) {
4543
+ if (slot.node !== void 0) {
4544
+ const x = cursorX + (width - slot.box.width) / 2;
4545
+ deltas.set(slot.node.id, [x - slot.box.x, cursorY - slot.box.y]);
4546
+ }
4547
+ cursorY += slot.box.height + gapV;
3622
4548
  }
3623
- cursorX += width + gapH;
4549
+ cursorX += width + captionRoom(r);
3624
4550
  });
3625
4551
  return translate(scene, deltas);
3626
4552
  }
4553
+ var TAU = Math.PI * 2;
4554
+ var normalizeAngle = (radians) => (radians % TAU + TAU) % TAU;
4555
+ function meanAngle(angles) {
4556
+ let x = 0;
4557
+ let y = 0;
4558
+ for (const angle of angles) {
4559
+ x += Math.cos(angle);
4560
+ y += Math.sin(angle);
4561
+ }
4562
+ return Math.atan2(y, x);
4563
+ }
4564
+ var ringExtent = (element) => Math.max(element.width, element.height);
4565
+ function adjacency(scene, nodes) {
4566
+ const inSet = new Set(nodes.map((node) => node.id));
4567
+ const neighbours = /* @__PURE__ */ new Map();
4568
+ for (const node of nodes) neighbours.set(node.id, /* @__PURE__ */ new Set());
4569
+ for (const element of scene) {
4570
+ if (element.type !== "arrow") continue;
4571
+ const from = startOf(element);
4572
+ const to = endOf(element);
4573
+ if (from === null || to === null || from === to) continue;
4574
+ if (!inSet.has(from) || !inSet.has(to)) continue;
4575
+ neighbours.get(from)?.add(to);
4576
+ neighbours.get(to)?.add(from);
4577
+ }
4578
+ return neighbours;
4579
+ }
4580
+ function pickHub(neighbours, ordered) {
4581
+ const first = ordered[0];
4582
+ if (first === void 0) return void 0;
4583
+ const degree = (element) => neighbours.get(element.id)?.size ?? 0;
4584
+ let hub = first;
4585
+ for (const node of ordered) if (degree(node) > degree(hub)) hub = node;
4586
+ return hub;
4587
+ }
4588
+ function layoutRadial(scene, targets, spacing) {
4589
+ const nodes = targets.filter((element) => !isLinear(element) && !isAnchored(element));
4590
+ const ordered = [...nodes].sort((a, b) => a.id < b.id ? -1 : a.id > b.id ? 1 : 0);
4591
+ const neighbours = adjacency(scene, nodes);
4592
+ const hub = pickHub(neighbours, ordered);
4593
+ if (hub === void 0) return [];
4594
+ const byId = new Map(nodes.map((node) => [node.id, node]));
4595
+ const gap = spacing ?? HOUSE.spacing.h;
4596
+ const ringOf = /* @__PURE__ */ new Map([[hub.id, 0]]);
4597
+ const parentsOf = /* @__PURE__ */ new Map();
4598
+ let frontier = [hub.id];
4599
+ let depth = 0;
4600
+ while (frontier.length > 0) {
4601
+ depth += 1;
4602
+ const discovered = /* @__PURE__ */ new Map();
4603
+ for (const id of frontier) {
4604
+ for (const other of [...neighbours.get(id) ?? []].sort()) {
4605
+ if (ringOf.has(other)) continue;
4606
+ const found = discovered.get(other);
4607
+ if (found === void 0) discovered.set(other, [id]);
4608
+ else found.push(id);
4609
+ }
4610
+ }
4611
+ for (const [child, parents] of discovered) {
4612
+ ringOf.set(child, depth);
4613
+ parentsOf.set(child, parents);
4614
+ }
4615
+ frontier = [...discovered.keys()];
4616
+ }
4617
+ for (const node of ordered) {
4618
+ if (!ringOf.has(node.id)) ringOf.set(node.id, depth + 1);
4619
+ }
4620
+ const rings = /* @__PURE__ */ new Map();
4621
+ for (const node of ordered) {
4622
+ const ring = ringOf.get(node.id) ?? 0;
4623
+ const bucket = rings.get(ring);
4624
+ if (bucket === void 0) rings.set(ring, [node.id]);
4625
+ else bucket.push(node.id);
4626
+ }
4627
+ const [cx, cy] = centre(boundsOfAll(nodes.map(boxOf)));
4628
+ const deltas = /* @__PURE__ */ new Map();
4629
+ deltas.set(hub.id, [cx - (hub.x + hub.width / 2), cy - (hub.y + hub.height / 2)]);
4630
+ const angleOf = /* @__PURE__ */ new Map([[hub.id, 0]]);
4631
+ let cleared = ringExtent(hub) / 2;
4632
+ for (const ring of [...rings.keys()].filter((r) => r > 0).sort((a, b) => a - b)) {
4633
+ const members = rings.get(ring) ?? [];
4634
+ if (members.length === 0) continue;
4635
+ const scored = members.map((id) => {
4636
+ const known = (parentsOf.get(id) ?? []).map((parent) => angleOf.get(parent)).filter((angle) => angle !== void 0);
4637
+ return {
4638
+ id,
4639
+ at: known.length === 0 ? Number.POSITIVE_INFINITY : normalizeAngle(meanAngle(known))
4640
+ };
4641
+ });
4642
+ scored.sort((a, b) => a.at - b.at || (a.id < b.id ? -1 : a.id > b.id ? 1 : 0));
4643
+ const extents = members.map((id) => {
4644
+ const node = byId.get(id);
4645
+ return node === void 0 ? 0 : ringExtent(node);
4646
+ });
4647
+ const widest = Math.max(...extents);
4648
+ const seating = extents.reduce((sum, extent) => sum + extent + gap, 0) / TAU;
4649
+ const radius = Math.max(seating, cleared + widest / 2 + gap);
4650
+ scored.forEach(({ id }, i) => {
4651
+ const theta = -Math.PI / 2 + TAU * i / members.length;
4652
+ angleOf.set(id, normalizeAngle(theta));
4653
+ const node = byId.get(id);
4654
+ if (node === void 0) return;
4655
+ deltas.set(id, [
4656
+ cx + radius * Math.cos(theta) - node.width / 2 - node.x,
4657
+ cy + radius * Math.sin(theta) - node.height / 2 - node.y
4658
+ ]);
4659
+ });
4660
+ cleared = radius + widest / 2;
4661
+ }
4662
+ return translate(scene, deltas);
4663
+ }
4664
+ function layoutTree(scene, targets, spacing) {
4665
+ const nodes = targets.filter((element) => !isLinear(element) && !isAnchored(element));
4666
+ const ordered = [...nodes].sort((a, b) => a.id < b.id ? -1 : a.id > b.id ? 1 : 0);
4667
+ const neighbours = adjacency(scene, nodes);
4668
+ const root = pickHub(neighbours, ordered);
4669
+ if (root === void 0) return [];
4670
+ const byId = new Map(nodes.map((node) => [node.id, node]));
4671
+ const gapH = spacing ?? HOUSE.spacing.h;
4672
+ const gapV = HOUSE.spacing.v;
4673
+ const depthOf = /* @__PURE__ */ new Map([[root.id, 0]]);
4674
+ const childrenOf2 = /* @__PURE__ */ new Map();
4675
+ const order = [];
4676
+ const stack = [root.id];
4677
+ const seen = /* @__PURE__ */ new Set([root.id]);
4678
+ while (stack.length > 0) {
4679
+ const id = stack.pop();
4680
+ if (id === void 0) continue;
4681
+ order.push(id);
4682
+ const kids = [];
4683
+ const adjacent = [...neighbours.get(id) ?? []].sort();
4684
+ for (const other of adjacent) {
4685
+ if (seen.has(other)) continue;
4686
+ seen.add(other);
4687
+ depthOf.set(other, (depthOf.get(id) ?? 0) + 1);
4688
+ kids.push(other);
4689
+ }
4690
+ childrenOf2.set(id, kids);
4691
+ for (let i = kids.length - 1; i >= 0; i--) {
4692
+ const kid = kids[i];
4693
+ if (kid !== void 0) stack.push(kid);
4694
+ }
4695
+ }
4696
+ for (const node of ordered) {
4697
+ if (seen.has(node.id)) continue;
4698
+ seen.add(node.id);
4699
+ depthOf.set(node.id, 1);
4700
+ order.push(node.id);
4701
+ }
4702
+ const columns = /* @__PURE__ */ new Map();
4703
+ for (const id of order) {
4704
+ const d = depthOf.get(id) ?? 0;
4705
+ const bucket = columns.get(d);
4706
+ if (bucket === void 0) columns.set(d, [id]);
4707
+ else bucket.push(id);
4708
+ }
4709
+ const captions = labelsByContainer(scene);
4710
+ const crossing = /* @__PURE__ */ new Map();
4711
+ for (const element of scene) {
4712
+ if (element.type !== "arrow") continue;
4713
+ const from = startOf(element);
4714
+ const to = endOf(element);
4715
+ if (from === null || to === null) continue;
4716
+ const a = depthOf.get(from);
4717
+ const b = depthOf.get(to);
4718
+ if (a === void 0 || b === void 0 || a === b) continue;
4719
+ const caption = captions.get(element.id);
4720
+ if (caption === void 0) continue;
4721
+ const gap = Math.min(a, b);
4722
+ crossing.set(gap, Math.max(crossing.get(gap) ?? 0, caption.width));
4723
+ }
4724
+ const anchor = boundsOfAll(nodes.map(boxOf));
4725
+ const depths = [...columns.keys()].sort((a, b) => a - b);
4726
+ const columnX = /* @__PURE__ */ new Map();
4727
+ let cursorX = anchor.x;
4728
+ for (const d of depths) {
4729
+ columnX.set(d, cursorX);
4730
+ const widest = Math.max(...(columns.get(d) ?? []).map((id) => byId.get(id)?.width ?? 0));
4731
+ const caption = crossing.get(d) ?? 0;
4732
+ cursorX += widest + Math.max(gapH, caption === 0 ? 0 : caption + HOUSE.sizes.labelPadX * 2);
4733
+ }
4734
+ const columnHeight = (d) => (columns.get(d) ?? []).reduce((sum, id) => sum + (byId.get(id)?.height ?? 0) + gapV, -gapV);
4735
+ const tallest = Math.max(...depths.map(columnHeight));
4736
+ const deltas = /* @__PURE__ */ new Map();
4737
+ for (const d of depths) {
4738
+ let cursorY = anchor.y + (tallest - columnHeight(d)) / 2;
4739
+ for (const id of columns.get(d) ?? []) {
4740
+ const node = byId.get(id);
4741
+ if (node === void 0) continue;
4742
+ const x = columnX.get(d) ?? anchor.x;
4743
+ deltas.set(id, [x - node.x, cursorY - node.y]);
4744
+ cursorY += node.height + gapV;
4745
+ }
4746
+ }
4747
+ return translate(scene, deltas);
4748
+ }
3627
4749
 
3628
4750
  // src/app/usecases/write/arrange-elements.ts
3629
4751
  function arrangeElements(deps) {
@@ -3644,6 +4766,7 @@ function arrangeElements(deps) {
3644
4766
  sceneId: applied.value.sceneId,
3645
4767
  sceneVersion: applied.value.sceneVersion,
3646
4768
  broadcastTo: applied.value.broadcastTo,
4769
+ overlapWarning: applied.value.overlapWarning,
3647
4770
  moved: [...applied.value.applied]
3648
4771
  });
3649
4772
  };
@@ -3798,82 +4921,6 @@ function createDebugElement(deps) {
3798
4921
  };
3799
4922
  }
3800
4923
 
3801
- // src/domain/scene/skeleton.ts
3802
- var isContainer = (type) => type === "rectangle" || type === "diamond" || type === "ellipse";
3803
- function explicit(input) {
3804
- const kept = {};
3805
- for (const [key, value] of Object.entries(input)) {
3806
- if (value === void 0) continue;
3807
- if (key === "type" || key === "id" || key === "role" || key === "label") continue;
3808
- kept[key] = value;
3809
- }
3810
- return kept;
3811
- }
3812
- var defaultSize = () => serviceSize();
3813
- function normalizeElements(input) {
3814
- return input.elements.map((element, i) => {
3815
- const fallback = input.ids[i];
3816
- if (fallback === void 0) {
3817
- throw new Error(`normalizeElements needs one id per element: none at position ${i}`);
3818
- }
3819
- const id = element.id ?? fallback;
3820
- const role = styleForRole(element.role ?? "compute");
3821
- const set = explicit(element);
3822
- if (element.type === "text") {
3823
- const text = element.text ?? element.label ?? "";
3824
- const skeleton2 = {
3825
- type: "text",
3826
- id,
3827
- x: element.x,
3828
- y: element.y,
3829
- fontSize: HOUSE.fontSize,
3830
- fontFamily: HOUSE.fontFamily,
3831
- // A free-standing note is not a service box: it takes the role's ink but never
3832
- // its fill, because a filled rectangle behind a caption reads as a shape.
3833
- strokeColor: role.strokeColor,
3834
- ...set,
3835
- text
3836
- };
3837
- return skeleton2;
3838
- }
3839
- if (element.type === "line") {
3840
- const skeleton2 = {
3841
- type: "line",
3842
- id,
3843
- x: element.x,
3844
- y: element.y,
3845
- strokeColor: role.strokeColor,
3846
- strokeWidth: HOUSE.strokeWidth,
3847
- roughness: HOUSE.roughness,
3848
- ...set,
3849
- ...element.label === void 0 ? {} : { label: houseLabel(element.label) }
3850
- };
3851
- return skeleton2;
3852
- }
3853
- if (!isContainer(element.type)) {
3854
- throw new Error(`normalizeElements cannot build a ${element.type}`);
3855
- }
3856
- const skeleton = {
3857
- type: element.type,
3858
- id,
3859
- x: element.x,
3860
- y: element.y,
3861
- ...defaultSize(),
3862
- ...role,
3863
- ...set,
3864
- ...element.label === void 0 ? {} : { label: houseLabel(element.label) }
3865
- };
3866
- return skeleton;
3867
- });
3868
- }
3869
- var LAYOUT_HINT_THRESHOLD = 4;
3870
- function layoutHint(elements) {
3871
- if (elements.length < LAYOUT_HINT_THRESHOLD) return null;
3872
- const positions = new Set(elements.map((element) => `${element.x},${element.y}`));
3873
- if (positions.size > 2) return null;
3874
- return `${elements.length} elements with no explicit layout \u2014 chalk_arrange(op: "layout-layered") will place them`;
3875
- }
3876
-
3877
4924
  // src/app/usecases/write/create-elements.ts
3878
4925
  function createElements(deps) {
3879
4926
  return async (input) => {
@@ -3901,20 +4948,6 @@ function createElements(deps) {
3901
4948
  }
3902
4949
 
3903
4950
  // src/app/usecases/write/frame-elements.ts
3904
- function boundsAround(children) {
3905
- const pad2 = HOUSE.sizes.framePad;
3906
- const left = Math.min(...children.map((c) => c.x));
3907
- const top = Math.min(...children.map((c) => c.y));
3908
- const right = Math.max(...children.map((c) => c.x + c.width));
3909
- const bottom = Math.max(...children.map((c) => c.y + c.height));
3910
- return {
3911
- x: left - pad2,
3912
- y: top - pad2,
3913
- width: right - left + pad2 * 2,
3914
- // Room at the top for the frame's own name, which renders above its border.
3915
- height: bottom - top + pad2 * 2
3916
- };
3917
- }
3918
4951
  function frameElements(deps) {
3919
4952
  return async (input) => {
3920
4953
  const scene = deps.write.resolve(input.sceneId);
@@ -3943,6 +4976,18 @@ function frameElements(deps) {
3943
4976
  if (!created.ok) return created;
3944
4977
  const committed = deps.write.append(scene.value, created.value);
3945
4978
  if (!committed.ok) return committed;
4979
+ const after = deps.write.resolve(input.sceneId);
4980
+ if (after.ok) {
4981
+ const built = after.value.elements.find((element) => element.id === elementId);
4982
+ if (built !== void 0) {
4983
+ const released = releaseCrossingArrows(after.value.elements, built);
4984
+ if (released.length > 0) {
4985
+ const freed = deps.write.apply(after.value, released);
4986
+ if (!freed.ok) return freed;
4987
+ return ok({ ...freed.value, frameId, children: named2.map((e) => e.id) });
4988
+ }
4989
+ }
4990
+ }
3946
4991
  return ok({ ...committed.value, frameId, children: named2.map((e) => e.id) });
3947
4992
  }
3948
4993
  if (input.frameId === void 0) {
@@ -3960,12 +5005,14 @@ function frameElements(deps) {
3960
5005
  if (members.length > 0) {
3961
5006
  changed.push({ ...frame, ...boundsAround(members) });
3962
5007
  }
5008
+ changed.push(...releaseCrossingArrows([...scene.value.elements, ...changed], frame));
3963
5009
  const applied = deps.write.apply(scene.value, changed);
3964
5010
  if (!applied.ok) return applied;
3965
5011
  return ok({
3966
5012
  sceneId: applied.value.sceneId,
3967
5013
  sceneVersion: applied.value.sceneVersion,
3968
5014
  broadcastTo: applied.value.broadcastTo,
5015
+ overlapWarning: applied.value.overlapWarning,
3969
5016
  frameId: input.frameId,
3970
5017
  children: members.map((element) => element.id)
3971
5018
  });
@@ -3991,6 +5038,7 @@ function groupElements(deps) {
3991
5038
  sceneId: applied.value.sceneId,
3992
5039
  sceneVersion: applied.value.sceneVersion,
3993
5040
  broadcastTo: applied.value.broadcastTo,
5041
+ overlapWarning: applied.value.overlapWarning,
3994
5042
  groupId,
3995
5043
  grouped: applied.value.applied
3996
5044
  });
@@ -4011,6 +5059,7 @@ function ungroupElements(deps) {
4011
5059
  sceneId: applied.value.sceneId,
4012
5060
  sceneVersion: applied.value.sceneVersion,
4013
5061
  broadcastTo: applied.value.broadcastTo,
5062
+ overlapWarning: applied.value.overlapWarning,
4014
5063
  ungrouped: applied.value.applied
4015
5064
  });
4016
5065
  };
@@ -4026,6 +5075,29 @@ var iconBox = (art, size) => {
4026
5075
  };
4027
5076
  };
4028
5077
  var svgDataUrl = (svg) => DataUrl.parse(`data:image/svg+xml;base64,${Buffer.from(svg, "utf8").toString("base64")}`);
5078
+ function placementOf(elements, input, box) {
5079
+ if (input.attachTo !== void 0) {
5080
+ const anchor = elements.find((element) => element.id === input.attachTo && !element.isDeleted);
5081
+ if (anchor === void 0) return fail(unknownElement(input.attachTo));
5082
+ const sides = input.placement !== void 0 ? [input.placement] : ["above", "below", "right", "left"];
5083
+ const ignore = /* @__PURE__ */ new Set([anchor.id]);
5084
+ for (const side of sides) {
5085
+ const at = anchoredPosition(anchor, box, side);
5086
+ const candidate = { ...at, width: box.width, height: box.height };
5087
+ if (sides.length === 1 || !wouldCollide(candidate, elements, ignore)) {
5088
+ return ok({ ...at, anchorIds: [anchor.id] });
5089
+ }
5090
+ }
5091
+ return ok({
5092
+ ...anchoredPosition(anchor, box, "above"),
5093
+ anchorIds: [anchor.id]
5094
+ });
5095
+ }
5096
+ if (input.x === void 0 || input.y === void 0) {
5097
+ return fail(iconNeedsAPlace(input.iconId));
5098
+ }
5099
+ return ok({ x: input.x, y: input.y, anchorIds: null });
5100
+ }
4029
5101
  function placeIcon(deps) {
4030
5102
  return async (input) => {
4031
5103
  const opened = await deps.icons();
@@ -4044,12 +5116,21 @@ function placeIcon(deps) {
4044
5116
  if (!resolved.ok) return resolved;
4045
5117
  const scene = resolved.value;
4046
5118
  const size = input.size ?? HOUSE.sizes.icon;
4047
- const chalk = { author: "claude", kind: "icon", iconId: icon.id };
5119
+ const box = icon.art.kind === "elements" ? { width: size, height: size } : iconBox(icon.art, size);
5120
+ const spot = placementOf(scene.elements, input, box);
5121
+ if (!spot.ok) return spot;
5122
+ const { x, y, anchorIds } = spot.value;
5123
+ const chalk = {
5124
+ author: "claude",
5125
+ kind: "icon",
5126
+ iconId: icon.id,
5127
+ ...anchorIds === null ? {} : { anchorIds }
5128
+ };
4048
5129
  if (icon.art.kind === "elements") {
4049
5130
  const ids = deps.idGen.elementIds(icon.art.elements.length);
4050
5131
  const placed = deps.placer.place({
4051
5132
  elements: icon.art.elements,
4052
- at: { x: input.x, y: input.y },
5133
+ at: { x, y },
4053
5134
  ids
4054
5135
  });
4055
5136
  const stamped = placed.map(
@@ -4066,7 +5147,6 @@ function placeIcon(deps) {
4066
5147
  iconId: icon.id
4067
5148
  });
4068
5149
  }
4069
- const box = iconBox(icon.art, size);
4070
5150
  const fileId = FileId.parse(deps.idGen.token());
4071
5151
  const file = {
4072
5152
  id: fileId,
@@ -4079,8 +5159,8 @@ function placeIcon(deps) {
4079
5159
  {
4080
5160
  type: "image",
4081
5161
  id: elementId,
4082
- x: input.x,
4083
- y: input.y,
5162
+ x,
5163
+ y,
4084
5164
  width: box.width,
4085
5165
  height: box.height,
4086
5166
  fileId,
@@ -4092,14 +5172,22 @@ function placeIcon(deps) {
4092
5172
  skeletons.push({
4093
5173
  type: "text",
4094
5174
  id: labelId,
4095
- x: input.x,
4096
- y: input.y + box.height + 8,
5175
+ ...captionBelow({ x, y }, box),
4097
5176
  text: input.label,
4098
5177
  width: box.width,
4099
5178
  fontSize: HOUSE.fontSize,
4100
5179
  fontFamily: HOUSE.fontFamily,
4101
5180
  textAlign: "center",
4102
- customData: { chalk: { author: "claude", kind: "note", iconId: icon.id } }
5181
+ // The caption carries the anchor too, or the glyph travels with its box and the
5182
+ // word it is captioned with stays behind.
5183
+ customData: {
5184
+ chalk: {
5185
+ author: "claude",
5186
+ kind: "note",
5187
+ iconId: icon.id,
5188
+ ...anchorIds === null ? {} : { anchorIds }
5189
+ }
5190
+ }
4103
5191
  });
4104
5192
  }
4105
5193
  const expanded = await deps.write.create(scene, skeletons);
@@ -4185,7 +5273,10 @@ function writeContext(deps) {
4185
5273
  return ok({
4186
5274
  sceneId: scene.id,
4187
5275
  sceneVersion: updated.sceneVersion,
4188
- broadcastTo: broadcast(scene, updated.sceneVersion, pushed, patch?.files)
5276
+ broadcastTo: broadcast(scene, updated.sceneVersion, pushed, patch?.files),
5277
+ // The scene as it now stands, not the batch: a new box buries whatever was
5278
+ // already underneath it, and the batch alone cannot see that.
5279
+ overlapWarning: overlapWarning([...kept, ...expansion.created])
4189
5280
  });
4190
5281
  },
4191
5282
  apply(scene, changed, options) {
@@ -4195,7 +5286,10 @@ function writeContext(deps) {
4195
5286
  sceneVersion: scene.sceneVersion,
4196
5287
  broadcastTo: 0,
4197
5288
  applied: [],
4198
- rejected: []
5289
+ rejected: [],
5290
+ // Nothing was written, so nothing was made worse. Staying quiet here keeps the
5291
+ // warning meaning "your write did this".
5292
+ overlapWarning: null
4199
5293
  });
4200
5294
  }
4201
5295
  deps.markPresence("writing");
@@ -4224,7 +5318,8 @@ function writeContext(deps) {
4224
5318
  sceneVersion: scene.sceneVersion,
4225
5319
  broadcastTo: 0,
4226
5320
  applied: [...merged.applied],
4227
- rejected
5321
+ rejected,
5322
+ overlapWarning: null
4228
5323
  });
4229
5324
  }
4230
5325
  const updated = deps.commit(scene.id, merged.elements);
@@ -4236,7 +5331,8 @@ function writeContext(deps) {
4236
5331
  sceneVersion: updated.sceneVersion,
4237
5332
  broadcastTo: broadcast(scene, updated.sceneVersion, pushed),
4238
5333
  applied: [...merged.applied],
4239
- rejected
5334
+ rejected,
5335
+ overlapWarning: overlapWarning(merged.elements)
4240
5336
  });
4241
5337
  }
4242
5338
  };
@@ -4326,6 +5422,24 @@ function rawElements(deps) {
4326
5422
  };
4327
5423
  }
4328
5424
 
5425
+ // src/domain/scene/captions.ts
5426
+ function fitCaption(container, caption) {
5427
+ if (caption.type !== "text") return caption;
5428
+ const source = caption.originalText === "" ? caption.text : caption.originalText;
5429
+ const inner = Math.max(1, container.width - HOUSE.sizes.labelPadX * 2);
5430
+ const lines = wrapLines(source, caption.fontSize, inner);
5431
+ const width = Math.max(...lines.map((line) => measureLine(line, caption.fontSize)));
5432
+ const height = lines.length * caption.fontSize * caption.lineHeight;
5433
+ return {
5434
+ ...caption,
5435
+ text: lines.join("\n"),
5436
+ width,
5437
+ height,
5438
+ x: container.x + (container.width - width) / 2,
5439
+ y: container.y + (container.height - height) / 2
5440
+ };
5441
+ }
5442
+
4329
5443
  // src/app/usecases/write/update-elements.ts
4330
5444
  var missing = (id) => toRejection(id, unknownElement(id));
4331
5445
  var onlyRequested = (landed, requested) => {
@@ -4378,17 +5492,37 @@ function updateElements(deps) {
4378
5492
  }
4379
5493
  }
4380
5494
  if (moved.size > 0) {
5495
+ const deltas = /* @__PURE__ */ new Map();
5496
+ for (const [id, next] of moved) {
5497
+ const before = byId.get(id);
5498
+ if (before !== void 0) deltas.set(id, [next.x - before.x, next.y - before.y]);
5499
+ }
5500
+ for (const [id, [dx, dy]] of withAnchoredElements(scene.value.elements, deltas)) {
5501
+ if (deltas.has(id) || dx === 0 && dy === 0) continue;
5502
+ const element = byId.get(id);
5503
+ if (element === void 0) continue;
5504
+ const followed = { ...element, x: element.x + dx, y: element.y + dy };
5505
+ moved.set(id, followed);
5506
+ knockOn.push(followed);
5507
+ }
4381
5508
  for (const [id, next] of moved) {
4382
5509
  const before = byId.get(id);
4383
5510
  const caption = captionOf(scene.value.elements, id);
4384
5511
  if (before === void 0 || caption === void 0) continue;
4385
- knockOn.push({
4386
- ...caption,
4387
- x: caption.x + (next.x - before.x),
4388
- y: caption.y + (next.y - before.y)
4389
- });
5512
+ const resized = next.width !== before.width || next.height !== before.height;
5513
+ knockOn.push(
5514
+ resized ? fitCaption(next, caption) : {
5515
+ ...caption,
5516
+ x: caption.x + (next.x - before.x),
5517
+ y: caption.y + (next.y - before.y)
5518
+ }
5519
+ );
4390
5520
  }
4391
5521
  knockOn.push(...redrawArrowsTouching(scene.value.elements, moved));
5522
+ const patched = new Set(changed.map((element) => element.id));
5523
+ knockOn.push(
5524
+ ...refitFrames(scene.value.elements, moved).filter((frame) => !patched.has(frame.id))
5525
+ );
4392
5526
  }
4393
5527
  for (const element of knockOn) {
4394
5528
  const before = byId.get(element.id);
@@ -4402,6 +5536,7 @@ function updateElements(deps) {
4402
5536
  sceneId: applied.value.sceneId,
4403
5537
  sceneVersion: applied.value.sceneVersion,
4404
5538
  broadcastTo: applied.value.broadcastTo,
5539
+ overlapWarning: applied.value.overlapWarning,
4405
5540
  // What the caller asked for, not everything that moved. A caption that followed its
4406
5541
  // box and an arrow that was redrawn are consequences; listing them would turn this
4407
5542
  // into "every element that changed" and leave the caller working out which of them
@@ -4455,6 +5590,7 @@ function deleteElements(deps) {
4455
5590
  sceneId: applied.value.sceneId,
4456
5591
  sceneVersion: applied.value.sceneVersion,
4457
5592
  broadcastTo: applied.value.broadcastTo,
5593
+ overlapWarning: applied.value.overlapWarning,
4458
5594
  deleted: onlyRequested(
4459
5595
  applied.value.applied,
4460
5596
  input.deletions.map((deletion) => deletion.id)
@@ -4509,7 +5645,7 @@ function readEnv(source) {
4509
5645
  }
4510
5646
 
4511
5647
  // src/composition/container.ts
4512
- var PACKAGE_VERSION = "1.0.4";
5648
+ var PACKAGE_VERSION = "1.1.1";
4513
5649
  function readRuntime() {
4514
5650
  return {
4515
5651
  env: process.env,
@@ -5117,8 +6253,26 @@ function renderScene(description) {
5117
6253
  }
5118
6254
  lines.push("elements:");
5119
6255
  for (const element of description.elements) lines.push(row(element, true));
6256
+ lines.push(...adviceLines(description));
6257
+ lines.push(...overlapLines(description));
5120
6258
  return lines.join("\n");
5121
6259
  }
6260
+ function adviceLines(description) {
6261
+ return description.layoutAdvice === null ? [] : [`layout: ${description.layoutAdvice}`];
6262
+ }
6263
+ function overlapLines(description) {
6264
+ if (description.overlapCount === 0) return [];
6265
+ const shown = description.overlaps.length;
6266
+ const header = shown === description.overlapCount ? `overlaps: ${description.overlapCount}` : `overlaps: ${description.overlapCount} (worst ${shown} shown)`;
6267
+ return [
6268
+ header,
6269
+ ...description.overlaps.map(
6270
+ (overlap) => `! ${shortId2(overlap.a)} \xD7 ${shortId2(overlap.b)} ${px(overlap.width)}x${px(
6271
+ overlap.height
6272
+ )}`
6273
+ )
6274
+ ];
6275
+ }
5122
6276
  var arrowGlyph = (label) => label === null ? "->" : `-"${label}"->`;
5123
6277
  var insideLine = (edge) => ` ${shortId2(edge.from)} ${arrowGlyph(edge.label)} ${shortId2(edge.to)}`;
5124
6278
  var named = (id, label) => label === null ? shortId2(id) : `${shortId2(id)} "${label}"`;
@@ -5235,7 +6389,7 @@ var searchIconsTool = {
5235
6389
  };
5236
6390
  var placeIconTool = {
5237
6391
  name: "chalk_place_icon",
5238
- description: "Put an icon from the catalog on the canvas, at an id from chalk_search_icons. Give it a `label` \u2014 an unlabelled icon says a thing was here and not which thing. The icon id is recorded on the element, so chalk_describe_scene reports it as [icon:aws/lambda] rather than [image] and you can reason about it later without looking at pixels. Position it roughly and let chalk_arrange tidy up, as with everything else.",
6392
+ description: "Put an icon from the catalog on the canvas, at an id from chalk_search_icons. **Prefer `attachTo`** \u2014 the element the icon belongs to \u2014 over x/y: an attached icon travels with that element through every layout, while one at bare coordinates is near its box by coincidence and gets stranded the first time chalk_arrange runs. `placement` picks the side (above by default, or below/left/right/badge/inside). Give it a `label` unless it is attached to something already named \u2014 an unlabelled loose icon says a thing was here and not which thing. The icon id is recorded on the element, so chalk_describe_scene reports it as [icon:aws/lambda] rather than [image] and you can reason about it later without looking at pixels.",
5239
6393
  annotations: {
5240
6394
  title: "Place an icon",
5241
6395
  readOnlyHint: false,
@@ -5353,7 +6507,7 @@ var sceneSettingsTool = {
5353
6507
  // src/interfaces/mcp/tools/session.ts
5354
6508
  var screenshotTool = {
5355
6509
  name: "chalk_screenshot",
5356
- description: "Look at the canvas as an image. Use it when geometry matters and the description does not settle it \u2014 does this overlap, is that arrow crossing three boxes, does the layout read. Rendering happens in the user's browser, so the canvas has to be open; if no tab is connected the error says so. Prefer chalk_describe_scene for anything you can answer from structure: it is far smaller and it gives you ids you can act on.",
6510
+ description: "Look at the canvas as an image. Use it for questions only a picture answers \u2014 does the diagram read, does it look deliberate, is the visual weight right. Do NOT use it to find collisions: chalk_describe_scene reports every overlap with ids and sizes, and every write tool warns when it buried something. Rendering happens in the user's browser, so the canvas has to be open; if no tab is connected the error says so. Prefer chalk_describe_scene for anything answerable from structure \u2014 it is far smaller and it gives you ids you can act on.",
5357
6511
  annotations: {
5358
6512
  title: "Screenshot the canvas",
5359
6513
  readOnlyHint: true,
@@ -5509,7 +6663,7 @@ var createElementsTool = proxyTool({
5509
6663
  var connectTool = proxyTool({
5510
6664
  name: "chalk_connect",
5511
6665
  title: "Connect two elements",
5512
- description: "Draw an arrow between two elements. This is the ONLY way to make one: it binds both ends, so the arrow follows its boxes when the user drags them \u2014 an arrow made from coordinates comes loose the first time anybody tidies the diagram. Label it with what actually moves ('embeddings', 'POST /ingest', 'nightly'); an unlabelled arrow only says 'related', which the reader already assumed.",
6666
+ description: "Draw an arrow between two elements. This is the ONLY way to make one: it binds both ends, so the arrow follows its boxes when the user drags them \u2014 an arrow made from coordinates comes loose the first time anybody tidies the diagram. Label it with what actually moves ('embeddings', 'POST /ingest', 'nightly'); an unlabelled arrow only says 'related', which the reader already assumed. `arrowType` is the one option worth knowing by name: it defaults to `round`, and `elbow` gives right-angled routing that suits an architecture diagram far better \u2014 pick one and keep it consistent across the canvas. Ends and `strokeStyle` carry meaning too (`dashed` for async is the modifier a reader most often wants) \u2014 read chalk_read_me('arrows') before drawing a diagram\u2019s worth of them, or every edge comes out an identical straight solid line.",
5513
6667
  input: ConnectToolInput,
5514
6668
  output: ConnectOutput,
5515
6669
  call: (bridge) => bridge.connect
@@ -5535,7 +6689,7 @@ var deleteElementsTool = proxyTool({
5535
6689
  var arrangeTool = proxyTool({
5536
6690
  name: "chalk_arrange",
5537
6691
  title: "Arrange elements",
5538
- description: "Lay elements out. `layout-layered` is the one to reach for after drawing a graph: it ranks the boxes from the direction of the arrows between them and places them, which is far better than choosing twenty pairs of coordinates one at a time. The rest are tidying \u2014 align\xD76, distribute-h/v, grid, flip\xD72, and four z-order ops. Arrange only what you added; running a layout over a diagram the user has arranged will move their work.",
6692
+ description: "Lay elements out. Three of these read the arrows as well as the boxes and they answer different questions \u2014 picking the wrong one destroys the structure the right one would show. `layout-layered` ranks by arrow direction: reach for it when the diagram is a flow. `layout-radial` puts the most-connected box at the centre with the rest ringed around it. `layout-tree` puts it on the LEFT and fans the rest out rightwards, which reads better than a ring whenever the labels are wide \u2014 a ring has to be big enough to seat its members, and a big ring crosses its own middle. **chalk_describe_scene ends with a `layout:` line naming which of the three this graph wants and why** \u2014 read it rather than guessing. The rest are tidying: align\xD76, distribute-h/v, grid, flip\xD72, and four z-order ops. Arrange only what you added; running a layout over a diagram the user has arranged will move their work.",
5539
6693
  input: ArrangeToolInput,
5540
6694
  output: ArrangeOutput,
5541
6695
  annotations: { idempotentHint: true },
@@ -5853,6 +7007,9 @@ var STATUS = {
5853
7007
  // Phase 5.
5854
7008
  CATALOG_UNAVAILABLE: 500,
5855
7009
  UNKNOWN_ICON: 404,
7010
+ // 400: the icon exists and the request is malformed — it says nothing about where to
7011
+ // put the thing.
7012
+ ICON_NEEDS_A_PLACE: 400,
5856
7013
  UNKNOWN_PACK: 404,
5857
7014
  // 502, not 500: the failure is somebody else registry or somebody else archive, and
5858
7015
  // the distinction is what tells a user to check their network rather than file a bug.