chalkbridge 1.1.0 → 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 (152) hide show
  1. package/dist/{chunk-XDK5UUTO.js → chunk-XFH76JS6.js} +66 -5
  2. package/dist/cli.js +879 -174
  3. package/dist/index.js +1 -1
  4. package/guides/layout.md +28 -1
  5. package/package.json +1 -1
  6. package/ui-dist/assets/{abnfDiagram-VCTEODGH-BCawxiKB.js → abnfDiagram-VCTEODGH-Bwcewgyl.js} +1 -1
  7. package/ui-dist/assets/{ar-SA-G6X2FPQ2-UCm9FftA.js → ar-SA-G6X2FPQ2-58dCrzRZ.js} +1 -1
  8. package/ui-dist/assets/architecture-7GRP2DOG-Df8gKqbg.js +1 -0
  9. package/ui-dist/assets/{architectureDiagram-5GKGNRK7-DIw1pNEk.js → architectureDiagram-5GKGNRK7-CMPSnIrP.js} +1 -1
  10. package/ui-dist/assets/{az-AZ-76LH7QW2-B5LEIxkk.js → az-AZ-76LH7QW2-CCpEVexR.js} +1 -1
  11. package/ui-dist/assets/{bg-BG-XCXSNQG7-2bpThpZL.js → bg-BG-XCXSNQG7-DDlArZMH.js} +1 -1
  12. package/ui-dist/assets/{blockDiagram-NRAW4CY4-BGr2WGp_.js → blockDiagram-NRAW4CY4-D7TrKIo5.js} +1 -1
  13. package/ui-dist/assets/{bn-BD-2XOGV67Q-Drz-y_cX.js → bn-BD-2XOGV67Q-BXyhMN4V.js} +1 -1
  14. package/ui-dist/assets/{c4Diagram-UCG6FXSJ-vbm87bpC.js → c4Diagram-UCG6FXSJ-CazStwlL.js} +1 -1
  15. package/ui-dist/assets/{ca-ES-6MX7JW3Y-D89p_FZ-.js → ca-ES-6MX7JW3Y-JHxjajfS.js} +1 -1
  16. package/ui-dist/assets/channel-C4uQlBVf.js +1 -0
  17. package/ui-dist/assets/{chunk-75Z2AOVW-B7kf5jVz.js → chunk-75Z2AOVW-BJsQGYsA.js} +1 -1
  18. package/ui-dist/assets/{chunk-DU6HZSFF-BlGPS6B6.js → chunk-DU6HZSFF-C1xw9RZQ.js} +1 -1
  19. package/ui-dist/assets/{chunk-EIO257PC-nzXnwORk.js → chunk-EIO257PC-BrhyYOZD.js} +1 -1
  20. package/ui-dist/assets/{chunk-F27PBJKO-B7qepoj_.js → chunk-F27PBJKO-f6XMRQIS.js} +1 -1
  21. package/ui-dist/assets/{chunk-G27WJ6UU-CSjcvmOf.js → chunk-G27WJ6UU-Cws9GsBU.js} +1 -1
  22. package/ui-dist/assets/{chunk-GMAD6QVW-CtaZAO5u.js → chunk-GMAD6QVW-CbhLp4Jt.js} +1 -1
  23. package/ui-dist/assets/{chunk-JQ64N6SF-CuhtdTse.js → chunk-JQ64N6SF-B5lJbHea.js} +1 -1
  24. package/ui-dist/assets/{chunk-LCL6LL3I-DMfA3a_s.js → chunk-LCL6LL3I-bGYRjzHM.js} +1 -1
  25. package/ui-dist/assets/{chunk-OBVCFTLP-BoBuso2l.js → chunk-OBVCFTLP-CxeUZPN4.js} +1 -1
  26. package/ui-dist/assets/{chunk-P2QGCYS3-BZHK87sP.js → chunk-P2QGCYS3-CZEugM1f.js} +1 -1
  27. package/ui-dist/assets/{chunk-POPQ4Y6H-Dk_v51-0.js → chunk-POPQ4Y6H-CoDq2Q_g.js} +1 -1
  28. package/ui-dist/assets/{chunk-PWAF6VOD-DQTvHbxy.js → chunk-PWAF6VOD-CX_HLotF.js} +1 -1
  29. package/ui-dist/assets/{chunk-R7TYR2AO-CQ_StUs1.js → chunk-R7TYR2AO-B4M26mv9.js} +1 -1
  30. package/ui-dist/assets/{chunk-RHFEMEQ7-BTgGEQit.js → chunk-RHFEMEQ7-CVXQBX33.js} +1 -1
  31. package/ui-dist/assets/{chunk-SVP7TREG-lkVXeHxJ.js → chunk-SVP7TREG-L7TJWT_o.js} +1 -1
  32. package/ui-dist/assets/{chunk-TEH6E4GO-DIEC-0PO.js → chunk-TEH6E4GO-Dct0SlmV.js} +1 -1
  33. package/ui-dist/assets/classDiagram-DTDB5LWJ-DRbrF0Ha.js +1 -0
  34. package/ui-dist/assets/classDiagram-v2-JRS7N3AN-DRbrF0Ha.js +1 -0
  35. package/ui-dist/assets/{cs-CZ-2BRQDIVT-CmSaQCAW.js → cs-CZ-2BRQDIVT-CvvJph-H.js} +1 -1
  36. package/ui-dist/assets/{cynefin-OW5HDTMX-B6f8qooM.js → cynefin-OW5HDTMX-Ei_Xgi2_.js} +1 -1
  37. package/ui-dist/assets/{cynefinDiagram-5FMLGOSQ-DFu8uB2c.js → cynefinDiagram-5FMLGOSQ-AFFSo_cm.js} +1 -1
  38. package/ui-dist/assets/{da-DK-5WZEPLOC-DZ68cfJh.js → da-DK-5WZEPLOC-BaezbVeN.js} +1 -1
  39. package/ui-dist/assets/{dagre-3AP2YEHR-CuUE3cEY.js → dagre-3AP2YEHR-B-FVEWH2.js} +1 -1
  40. package/ui-dist/assets/{de-DE-XR44H4JA-ClBUqbwZ.js → de-DE-XR44H4JA-BAGdMA-L.js} +1 -1
  41. package/ui-dist/assets/{diagram-S7CK7UJ4-DjLndaJK.js → diagram-S7CK7UJ4-eyl1yJj7.js} +1 -1
  42. package/ui-dist/assets/{diagram-UQ7AKVKN-wLhxEBwN.js → diagram-UQ7AKVKN-CugR7jCP.js} +1 -1
  43. package/ui-dist/assets/{diagram-VSXAHHWV-CVek4gvW.js → diagram-VSXAHHWV-Dz85hV5k.js} +1 -1
  44. package/ui-dist/assets/{diagram-VX7I27RA-CXyuaDuW.js → diagram-VX7I27RA-BYSNxzTB.js} +1 -1
  45. package/ui-dist/assets/{diagram-Z3DM3KII-BGk8dw5c.js → diagram-Z3DM3KII-BN4pFJaP.js} +1 -1
  46. package/ui-dist/assets/{dist-DyDCC61R.js → dist-CVqcfLn4.js} +4 -4
  47. package/ui-dist/assets/{ebnfDiagram-PWID7BFC-Bq5wdWGX.js → ebnfDiagram-PWID7BFC-Ddprc5Rv.js} +1 -1
  48. package/ui-dist/assets/{el-GR-BZB4AONW-oCuJWxYE.js → el-GR-BZB4AONW-C8EbIpNQ.js} +1 -1
  49. package/ui-dist/assets/{en-B4ZKOASM-DevQWFhD.js → en-B4ZKOASM-BZp0XUv6.js} +1 -1
  50. package/ui-dist/assets/{erDiagram-SSCWMZ5O-CXZW4I_b.js → erDiagram-SSCWMZ5O-BoMeaxjf.js} +1 -1
  51. package/ui-dist/assets/{es-ES-U4NZUMDT-DgzMLG1J.js → es-ES-U4NZUMDT-CA2eQinb.js} +1 -1
  52. package/ui-dist/assets/{eu-ES-A7QVB2H4-1tA3vosZ.js → eu-ES-A7QVB2H4-CcaNArBV.js} +1 -1
  53. package/ui-dist/assets/eventmodeling-NTZA5JFV-CSPN2jeA.js +1 -0
  54. package/ui-dist/assets/{fa-IR-HGAKTJCU-BVbN6Ifb.js → fa-IR-HGAKTJCU-BH7DgnZN.js} +1 -1
  55. package/ui-dist/assets/{fi-FI-Z5N7JZ37-DsGMdP7T.js → fi-FI-Z5N7JZ37-DWg6APJ2.js} +1 -1
  56. package/ui-dist/assets/flowDiagram-A5DVABFB-YrkFRLG9.js +1 -0
  57. package/ui-dist/assets/{fr-FR-RHASNOE6-BEZa94DA.js → fr-FR-RHASNOE6-qBJplDsT.js} +1 -1
  58. package/ui-dist/assets/{ganttDiagram-EL5Y4UJY-BZxyNgRP.js → ganttDiagram-EL5Y4UJY-4wx5f9nv.js} +1 -1
  59. package/ui-dist/assets/{gitGraph-4MIJSDKK-Cz7F-_6d.js → gitGraph-4MIJSDKK-DDYlgTud.js} +1 -1
  60. package/ui-dist/assets/{gitGraphDiagram-WWUBYQGX-77gOaAOD.js → gitGraphDiagram-WWUBYQGX-DAhZkdQa.js} +1 -1
  61. package/ui-dist/assets/{gl-ES-HMX3MZ6V-DfSDz_sy.js → gl-ES-HMX3MZ6V-CTvRbK-4.js} +1 -1
  62. package/ui-dist/assets/{he-IL-6SHJWFNN-DT8nqxK2.js → he-IL-6SHJWFNN-w2aMYZZd.js} +1 -1
  63. package/ui-dist/assets/{hi-IN-IWLTKZ5I-BtL1eizk.js → hi-IN-IWLTKZ5I-DX4ZRE2H.js} +1 -1
  64. package/ui-dist/assets/{hu-HU-A5ZG7DT2-5FwjCLbT.js → hu-HU-A5ZG7DT2-naHDPNOM.js} +1 -1
  65. package/ui-dist/assets/{id-ID-SAP4L64H-DXfUbYET.js → id-ID-SAP4L64H-D5QxdnZa.js} +1 -1
  66. package/ui-dist/assets/image-GAAHSSAO-C9O0QZ3x.js +1 -0
  67. package/ui-dist/assets/{index-Boxo043A.js → index-W8DLsYN7.js} +8 -8
  68. package/ui-dist/assets/{info-A6RAGUB7-DIe8iET8.js → info-A6RAGUB7-RXb_MEIH.js} +1 -1
  69. package/ui-dist/assets/{infoDiagram-RXCK75RN-Dik-rzpk.js → infoDiagram-RXCK75RN-DzaEiHqC.js} +1 -1
  70. package/ui-dist/assets/{ishikawaDiagram-5VMMS53U-Ccb-hHW6.js → ishikawaDiagram-5VMMS53U-C9We7rRC.js} +1 -1
  71. package/ui-dist/assets/{it-IT-JPQ66NNP-6_sPHBVO.js → it-IT-JPQ66NNP-B0YDAONu.js} +1 -1
  72. package/ui-dist/assets/{ja-JP-DBVTYXUO-DgY_5_c8.js → ja-JP-DBVTYXUO-CjS5c0my.js} +1 -1
  73. package/ui-dist/assets/{journeyDiagram-EYS64GPL-CgzxmpyV.js → journeyDiagram-EYS64GPL-BnolpCVx.js} +1 -1
  74. package/ui-dist/assets/{kaa-6HZHGXH3-glmGx672.js → kaa-6HZHGXH3-BCxOG4kc.js} +1 -1
  75. package/ui-dist/assets/{kab-KAB-ZGHBKWFO--G7baVL0.js → kab-KAB-ZGHBKWFO-D3QotDYI.js} +1 -1
  76. package/ui-dist/assets/{kanban-definition-3QL26DDD-D6TnbrdQ.js → kanban-definition-3QL26DDD-Cy9HKz-0.js} +1 -1
  77. package/ui-dist/assets/{kk-KZ-P5N5QNE5-D3Vn0qgF.js → kk-KZ-P5N5QNE5-BrSjotfG.js} +1 -1
  78. package/ui-dist/assets/{km-KH-HSX4SM5Z-CAHt3o8M.js → km-KH-HSX4SM5Z-fb8nqdkK.js} +1 -1
  79. package/ui-dist/assets/{ko-KR-MTYHY66A-N3tCiPap.js → ko-KR-MTYHY66A-BXj8eDyy.js} +1 -1
  80. package/ui-dist/assets/{ku-TR-6OUDTVRD-CSZFwS--.js → ku-TR-6OUDTVRD-COZArCj9.js} +1 -1
  81. package/ui-dist/assets/{line-BhVZwKwh.js → line-7U1712L5.js} +1 -1
  82. package/ui-dist/assets/{lt-LT-XHIRWOB4-DuSlkfc-.js → lt-LT-XHIRWOB4-C-HJQqop.js} +1 -1
  83. package/ui-dist/assets/{lv-LV-5QDEKY6T-COuWGNLd.js → lv-LV-5QDEKY6T-DLfr6jLA.js} +1 -1
  84. package/ui-dist/assets/{mermaid-parser.core-DlqbfUOM.js → mermaid-parser.core-DrLo74XG.js} +3 -3
  85. package/ui-dist/assets/{mindmap-definition-FBJOCRG2-D9wLjZXa.js → mindmap-definition-FBJOCRG2-Ba42gC8u.js} +1 -1
  86. package/ui-dist/assets/{mr-IN-CRQNXWMA-Bv4WHAzM.js → mr-IN-CRQNXWMA-B9soDV3M.js} +1 -1
  87. package/ui-dist/assets/{my-MM-5M5IBNSE-BNltBCCh.js → my-MM-5M5IBNSE-LZaFHx2a.js} +1 -1
  88. package/ui-dist/assets/{nb-NO-T6EIAALU-DgsGabVE.js → nb-NO-T6EIAALU-BwhFAqXH.js} +1 -1
  89. package/ui-dist/assets/{nl-NL-IS3SIHDZ-MPMxu0cH.js → nl-NL-IS3SIHDZ-D2FhUB0E.js} +1 -1
  90. package/ui-dist/assets/{nn-NO-6E72VCQL-CY-NT6bD.js → nn-NO-6E72VCQL-yn-NI0ZL.js} +1 -1
  91. package/ui-dist/assets/{oc-FR-POXYY2M6-BCWtA2dJ.js → oc-FR-POXYY2M6-DOhix0u-.js} +1 -1
  92. package/ui-dist/assets/{pa-IN-N4M65BXN-CU1D5Lce.js → pa-IN-N4M65BXN-9aoDSwWx.js} +1 -1
  93. package/ui-dist/assets/{packet-AYTQ26CC-DCMIIosG.js → packet-AYTQ26CC-DuyZkxuT.js} +1 -1
  94. package/ui-dist/assets/{pegDiagram-XKGWAZYB-CpU2aj-A.js → pegDiagram-XKGWAZYB-PH6THd_o.js} +1 -1
  95. package/ui-dist/assets/percentages-BXMCSKIN-BFxN7npZ.js +1 -0
  96. package/ui-dist/assets/{pie-WAS4IAKB-BEVkH0em.js → pie-WAS4IAKB-BdQJyLvD.js} +1 -1
  97. package/ui-dist/assets/{pieDiagram-E7YTZNPT-D-jyX8nJ.js → pieDiagram-E7YTZNPT-CPm011HM.js} +1 -1
  98. package/ui-dist/assets/{pl-PL-T2D74RX3-CwNWACCb.js → pl-PL-T2D74RX3-BO50j2s4.js} +1 -1
  99. package/ui-dist/assets/{pt-BR-5N22H2LF-DCs1W5P0.js → pt-BR-5N22H2LF-Cp8T2vmD.js} +1 -1
  100. package/ui-dist/assets/{pt-PT-UZXXM6DQ-Bm9Hlh9S.js → pt-PT-UZXXM6DQ-CdTZJ0Y5.js} +1 -1
  101. package/ui-dist/assets/{quadrantDiagram-AXDQQJYC-BqzJD4zL.js → quadrantDiagram-AXDQQJYC-Cm1Rlzhk.js} +1 -1
  102. package/ui-dist/assets/{radar-RG4KPBEZ-B6T3SZ4z.js → radar-RG4KPBEZ-48srnC1_.js} +1 -1
  103. package/ui-dist/assets/{railroad-74A4TZTK-BPKCaP4g.js → railroad-74A4TZTK-C6601ICA.js} +1 -1
  104. package/ui-dist/assets/railroad-abnf-HS5TGJTU-Dmq5JjW8.js +1 -0
  105. package/ui-dist/assets/railroad-ebnf-LZEXJU2U-DzMo_BK8.js +1 -0
  106. package/ui-dist/assets/railroad-peg-WCYAUIDC-LanIHXaC.js +1 -0
  107. package/ui-dist/assets/{railroadDiagram-O6MQD6OU-CzTuUKE-.js → railroadDiagram-O6MQD6OU-C8yR5TE7.js} +1 -1
  108. package/ui-dist/assets/{requirementDiagram-EFPCY7ZU-Bly8_reu.js → requirementDiagram-EFPCY7ZU-DeleqP-5.js} +1 -1
  109. package/ui-dist/assets/{ro-RO-JPDTUUEW-DbKkWzmT.js → ro-RO-JPDTUUEW-C7SipvaK.js} +1 -1
  110. package/ui-dist/assets/{ru-RU-B4JR7IUQ-C7y8hptt.js → ru-RU-B4JR7IUQ-dNLeoFpA.js} +1 -1
  111. package/ui-dist/assets/{sankeyDiagram-P5KCCOFB-BNe1jsYh.js → sankeyDiagram-P5KCCOFB-CSJKN7--.js} +1 -1
  112. package/ui-dist/assets/{sequenceDiagram-WJ2MYXX4-jpU3VDzA.js → sequenceDiagram-WJ2MYXX4-B_C10dhq.js} +1 -1
  113. package/ui-dist/assets/{si-LK-N5RQ5JYF-DsyN9eZC.js → si-LK-N5RQ5JYF-DogInl3-.js} +1 -1
  114. package/ui-dist/assets/{sk-SK-C5VTKIMK-C3GmutL2.js → sk-SK-C5VTKIMK-2YnR2SrM.js} +1 -1
  115. package/ui-dist/assets/{sl-SI-NN7IZMDC-C6dxM6aa.js → sl-SI-NN7IZMDC-w1ecqU-U.js} +1 -1
  116. package/ui-dist/assets/{stateDiagram-HBIQ2CUA-B2ZRnydN.js → stateDiagram-HBIQ2CUA-BIYHmokV.js} +1 -1
  117. package/ui-dist/assets/stateDiagram-v2-4QOOHH4V-BEVYrEze.js +1 -0
  118. package/ui-dist/assets/subset-shared.chunk-zuCoK3Ax.js +1 -0
  119. package/ui-dist/assets/{subset-worker.chunk-WItGSQq_.js → subset-worker.chunk-Fq9aq3Pk.js} +1 -1
  120. package/ui-dist/assets/{sv-SE-XGPEYMSR-BPU1z_vz.js → sv-SE-XGPEYMSR-ZA1w16aZ.js} +1 -1
  121. package/ui-dist/assets/{swimlanes-XN3QIQJK-BuYIN15M.js → swimlanes-XN3QIQJK-Bsz-wFaq.js} +1 -1
  122. package/ui-dist/assets/swimlanesDiagram-VK2B7HYN-DVXdrFIL.js +8 -0
  123. package/ui-dist/assets/{ta-IN-2NMHFXQM-AJVbdGTO.js → ta-IN-2NMHFXQM-CanbC1P6.js} +1 -1
  124. package/ui-dist/assets/{th-TH-HPSO5L25-B1vvYNmv.js → th-TH-HPSO5L25-DyHWEX0G.js} +1 -1
  125. package/ui-dist/assets/{timeline-definition-24CTP7MA-Bld3Oj9i.js → timeline-definition-24CTP7MA-4TgC1OoH.js} +1 -1
  126. package/ui-dist/assets/{tr-TR-DEFEU3FU-C_emJDca.js → tr-TR-DEFEU3FU-D0W6rufc.js} +1 -1
  127. package/ui-dist/assets/{treeView-Q6P3EWNA-CQEi50Re.js → treeView-Q6P3EWNA-C9fqdzP-.js} +1 -1
  128. package/ui-dist/assets/{treemap-WGGIJYW6-D3gRwGbr.js → treemap-WGGIJYW6-BqNZy40L.js} +1 -1
  129. package/ui-dist/assets/{uk-UA-QMV73CPH-B69Ddo0p.js → uk-UA-QMV73CPH-C1iCWdAQ.js} +1 -1
  130. package/ui-dist/assets/{vennDiagram-4TSXK5OY-BWtR81LD.js → vennDiagram-4TSXK5OY-BQFCZXqB.js} +1 -1
  131. package/ui-dist/assets/{vi-VN-M7AON7JQ-ByM0OkIF.js → vi-VN-M7AON7JQ-BWynaJhA.js} +1 -1
  132. package/ui-dist/assets/{wardley-WFR3VGLG-DpBUmo8s.js → wardley-WFR3VGLG-cNQ06Oix.js} +1 -1
  133. package/ui-dist/assets/{wardleyDiagram-VM6X3IG4-n39Dh9vZ.js → wardleyDiagram-VM6X3IG4-BbLDW2ka.js} +1 -1
  134. package/ui-dist/assets/{xychartDiagram-S5SC5T6Z-D8-lcdGr.js → xychartDiagram-S5SC5T6Z-r5VAgI1x.js} +1 -1
  135. package/ui-dist/assets/{zh-CN-LNUGB5OW-g_S93sCp.js → zh-CN-LNUGB5OW-BNvy0Mnj.js} +1 -1
  136. package/ui-dist/assets/{zh-HK-E62DVLB3-B3J_lyrF.js → zh-HK-E62DVLB3-Csv83LC3.js} +1 -1
  137. package/ui-dist/assets/{zh-TW-RAJ6MFWO-mYLP5aqQ.js → zh-TW-RAJ6MFWO-CW-6XIyO.js} +1 -1
  138. package/ui-dist/index.html +1 -1
  139. package/ui-dist/assets/architecture-7GRP2DOG-CiyYoVgW.js +0 -1
  140. package/ui-dist/assets/channel-DtgwkzBZ.js +0 -1
  141. package/ui-dist/assets/classDiagram-DTDB5LWJ-B83TJF6p.js +0 -1
  142. package/ui-dist/assets/classDiagram-v2-JRS7N3AN-B83TJF6p.js +0 -1
  143. package/ui-dist/assets/eventmodeling-NTZA5JFV-Bh0QsTUi.js +0 -1
  144. package/ui-dist/assets/flowDiagram-A5DVABFB-Chb6hSSG.js +0 -1
  145. package/ui-dist/assets/image-GAAHSSAO-AgKiDvHK.js +0 -1
  146. package/ui-dist/assets/percentages-BXMCSKIN-CMz17NMy.js +0 -1
  147. package/ui-dist/assets/railroad-abnf-HS5TGJTU-RL2kk25t.js +0 -1
  148. package/ui-dist/assets/railroad-ebnf-LZEXJU2U-NY4mALmg.js +0 -1
  149. package/ui-dist/assets/railroad-peg-WCYAUIDC-BNAC9MGy.js +0 -1
  150. package/ui-dist/assets/stateDiagram-v2-4QOOHH4V-BUscqu-N.js +0 -1
  151. package/ui-dist/assets/subset-shared.chunk-CRXudW0i.js +0 -1
  152. package/ui-dist/assets/swimlanesDiagram-VK2B7HYN-Cn_HC7uw.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, iconNeedsAPlace, DataUrl, Index, ROUNDNESS, STALE_SELECTION_MS, noScenes, ClientMessage, assertNever, PROTOCOL_VERSION, ApiError, EXCALIDRAW_FILE_VERSION, EXCALIDRAW_FILE_TYPE, point, FLAG_GLYPH, measureLine, wrapLines, KIND_ABBREVIATION, catalogUnavailable, FrameId, IconItem, ExcalidrawFile } from './chunk-XDK5UUTO.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';
@@ -2128,6 +2128,151 @@ function overlapWarning(elements) {
2128
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
2129
  }
2130
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
+
2131
2276
  // src/domain/scene/observe.ts
2132
2277
  var SHORT_ID_LENGTH = 4;
2133
2278
  var shortId2 = (id) => id.slice(0, SHORT_ID_LENGTH);
@@ -2245,6 +2390,12 @@ function observeScene(input) {
2245
2390
  elements: rows.map((element) => describeElement(element, context)),
2246
2391
  overlaps: overlaps.slice(0, MAX_REPORTED_OVERLAPS),
2247
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,
2248
2399
  detail: input.detail
2249
2400
  };
2250
2401
  }
@@ -3319,8 +3470,108 @@ function labelledSize(label, fontSize = HOUSE.fontSize) {
3319
3470
  return { width, height };
3320
3471
  }
3321
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
+ }
3557
+
3322
3558
  // src/app/usecases/write/annotate-elements.ts
3323
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
+ }
3324
3575
  function annotateElements(deps) {
3325
3576
  return async (input) => {
3326
3577
  const scene = deps.write.resolve(input.sceneId);
@@ -3335,14 +3586,20 @@ function annotateElements(deps) {
3335
3586
  if (anchors.length === 0) return fail(emptySelection("chalk_annotate"));
3336
3587
  const left = Math.min(...anchors.map((element) => element.x));
3337
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);
3338
3595
  const skeleton = {
3339
3596
  type: "text",
3340
3597
  id: deps.idGen.elementId(),
3341
- x: left,
3342
- y: bottom + NOTE_GAP,
3343
- text: input.text,
3598
+ x: spot.x,
3599
+ y: spot.y,
3600
+ ...wrapped,
3344
3601
  // A size below the labels, so a note reads as commentary rather than as content.
3345
- fontSize: 16,
3602
+ fontSize: NOTE_FONT_SIZE,
3346
3603
  fontFamily: HOUSE.fontFamily,
3347
3604
  strokeColor: styleForRole("external").strokeColor,
3348
3605
  customData: {
@@ -3397,52 +3654,6 @@ function focusElements(deps) {
3397
3654
  };
3398
3655
  }
3399
3656
 
3400
- // src/domain/scene/anchors.ts
3401
- var ANCHOR_GAP = 12;
3402
- var anchorsOf = (element) => element.customData?.chalk?.anchorIds ?? [];
3403
- var isAnchored = (element) => anchorsOf(element).length > 0;
3404
- function anchoredPosition(anchor, size, placement) {
3405
- const midX = anchor.x + anchor.width / 2 - size.width / 2;
3406
- const midY = anchor.y + anchor.height / 2 - size.height / 2;
3407
- switch (placement) {
3408
- case "above":
3409
- return { x: midX, y: anchor.y - size.height - ANCHOR_GAP };
3410
- case "below":
3411
- return { x: midX, y: anchor.y + anchor.height + ANCHOR_GAP };
3412
- case "left":
3413
- return { x: anchor.x - size.width - ANCHOR_GAP, y: midY };
3414
- case "right":
3415
- return { x: anchor.x + anchor.width + ANCHOR_GAP, y: midY };
3416
- case "badge":
3417
- return { x: anchor.x + anchor.width - size.width / 2, y: anchor.y - size.height / 2 };
3418
- case "inside":
3419
- return { x: midX, y: midY };
3420
- }
3421
- }
3422
- var captionBelow = (position, size) => ({
3423
- x: position.x,
3424
- y: position.y + size.height + 8
3425
- });
3426
- function withAnchoredElements(scene, deltas) {
3427
- const attached = scene.filter((element) => !element.isDeleted && isAnchored(element));
3428
- if (attached.length === 0) return deltas;
3429
- const widened = new Map(deltas);
3430
- const present2 = new Set(scene.filter((element) => !element.isDeleted).map((e) => e.id));
3431
- for (const element of attached) {
3432
- if (widened.has(element.id)) continue;
3433
- const live = anchorsOf(element).filter((id) => present2.has(id));
3434
- if (live.length === 0) continue;
3435
- const moves = live.map((id) => deltas.get(id));
3436
- const [first] = moves;
3437
- if (first === void 0) continue;
3438
- if (!moves.every((move) => move !== void 0 && move[0] === first[0] && move[1] === first[1])) {
3439
- continue;
3440
- }
3441
- widened.set(element.id, first);
3442
- }
3443
- return widened;
3444
- }
3445
-
3446
3657
  // src/domain/scene/frames.ts
3447
3658
  function boundsAround(children) {
3448
3659
  const pad2 = HOUSE.sizes.framePad;
@@ -3532,6 +3743,176 @@ function betweenIndices(lower, upper, count2) {
3532
3743
  return generateNKeysBetween(lower, upper, count2).map((key) => Index.parse(key));
3533
3744
  }
3534
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
+
3535
3916
  // src/domain/scene/arrange.ts
3536
3917
  var boxOf = (element) => ({
3537
3918
  x: element.x,
@@ -3574,6 +3955,38 @@ function edgePoint(from, to, gap) {
3574
3955
  const t = scale + push;
3575
3956
  return [cx + dx * t, cy + dy * t];
3576
3957
  }
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
+ }
3577
3990
  function redrawOne(current, labels, arrow, lane) {
3578
3991
  if (arrow.type !== "arrow") return [];
3579
3992
  const fromId = startOf(arrow);
@@ -3589,18 +4002,53 @@ function redrawOne(current, labels, arrow, lane) {
3589
4002
  ...edgePoint(boxOf(from), boxOf(to), startGap),
3590
4003
  ...edgePoint(boxOf(to), boxOf(from), endGap)
3591
4004
  ];
3592
- const points = elbowed ? elbowRoute(ex - sx, ey - sy) : [point(0, 0), point(ex - sx, ey - sy)];
3593
- const spans = points.reduce(
3594
- (box, [px2, py]) => ({
3595
- w: Math.max(box.w, Math.abs(px2)),
3596
- h: Math.max(box.h, Math.abs(py))
3597
- }),
3598
- { w: 0, h: 0 }
3599
- );
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
+ };
3600
4033
  const redrawn = {
3601
4034
  ...arrow,
3602
- x: sx,
3603
- y: 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],
3604
4052
  width: spans.w,
3605
4053
  height: spans.h,
3606
4054
  points,
@@ -3608,7 +4056,7 @@ function redrawOne(current, labels, arrow, lane) {
3608
4056
  };
3609
4057
  const label = labels.get(arrow.id);
3610
4058
  if (label === void 0) return [redrawn];
3611
- const mid = routeMidpoint({ x: sx, y: sy, points });
4059
+ const mid = routeMidpoint({ x: origin[0], y: origin[1], points });
3612
4060
  const at = { x: mid.x - label.width / 2, y: mid.y - label.height / 2 };
3613
4061
  return [redrawn, { ...label, ...at }];
3614
4062
  }
@@ -3621,6 +4069,7 @@ function faceBetween(from, to) {
3621
4069
  return dy >= 0 ? "bottom" : "top";
3622
4070
  }
3623
4071
  var LANE_SPREAD = 44;
4072
+ var LANE_INSET = 10;
3624
4073
  function laneOffsets(arrows, current) {
3625
4074
  const groups = /* @__PURE__ */ new Map();
3626
4075
  const add = (nodeId, face, node, slot) => {
@@ -3656,7 +4105,8 @@ function laneOffsets(arrows, current) {
3656
4105
  if (slots.length < 2) continue;
3657
4106
  slots.sort((a, b) => a.along - b.along || (a.arrow < b.arrow ? -1 : 1));
3658
4107
  const span = face === "right" || face === "left" ? node.height : node.width;
3659
- const spread = Math.min(LANE_SPREAD, span / (slots.length + 1));
4108
+ const usable = Math.max(0, span - LANE_INSET * 2);
4109
+ const spread = Math.min(LANE_SPREAD, usable / Math.max(1, slots.length - 1));
3660
4110
  slots.forEach((slot, i) => {
3661
4111
  const shift = (i - (slots.length - 1) / 2) * spread;
3662
4112
  const so_far = offsets.get(slot.arrow) ?? { start: 0, end: 0 };
@@ -3745,14 +4195,14 @@ var alignments = {
3745
4195
  "align-center-h": (box, bounds) => [0, bounds.y + bounds.height / 2 - (box.y + box.height / 2)],
3746
4196
  "align-center-v": (box, bounds) => [bounds.x + bounds.width / 2 - (box.x + box.width / 2), 0]
3747
4197
  };
3748
- function distribute(boxes, spacing, axis) {
3749
- const size = axis === "x" ? "width" : "height";
3750
- 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]);
3751
4201
  const deltas = boxes.map(() => [0, 0]);
3752
- let cursor = order[0]?.box[axis] ?? 0;
4202
+ let cursor = order[0]?.box[axis2] ?? 0;
3753
4203
  for (const { box, i } of order) {
3754
- const delta = cursor - box[axis];
3755
- deltas[i] = axis === "x" ? [delta, 0] : [0, delta];
4204
+ const delta = cursor - box[axis2];
4205
+ deltas[i] = axis2 === "x" ? [delta, 0] : [0, delta];
3756
4206
  cursor += box[size] + spacing;
3757
4207
  }
3758
4208
  return deltas;
@@ -3768,10 +4218,10 @@ function grid(boxes, options) {
3768
4218
  return [anchor.x + column * cellW - box.x, anchor.y + rowIndex * cellH - box.y];
3769
4219
  });
3770
4220
  }
3771
- function flip(boxes, axis) {
4221
+ function flip(boxes, axis2) {
3772
4222
  const bounds = boundsOfAll(boxes);
3773
4223
  return boxes.map((box) => {
3774
- if (axis === "x") {
4224
+ if (axis2 === "x") {
3775
4225
  const mirrored2 = bounds.x + bounds.width - (box.x - bounds.x) - box.width;
3776
4226
  return [mirrored2 - box.x, 0];
3777
4227
  }
@@ -3832,6 +4282,12 @@ function arrangeGeometry(input) {
3832
4282
  if (input.op === "layout-layered") {
3833
4283
  return ok({ moved: layoutLayered(input.scene, targets, input.spacing) });
3834
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
+ }
3835
4291
  const boxes = targets.map(boxOf);
3836
4292
  const spacing = input.spacing ?? HOUSE.spacing.h;
3837
4293
  const columns = input.columns ?? Math.ceil(Math.sqrt(targets.length));
@@ -3863,6 +4319,44 @@ function arrangeGeometry(input) {
3863
4319
  });
3864
4320
  return ok({ moved: translate(input.scene, byDelta) });
3865
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
+ }
3866
4360
  function layoutLayered(scene, targets, spacing) {
3867
4361
  const inSet = new Set(targets.map((element) => element.id));
3868
4362
  const edges = [];
@@ -3876,13 +4370,18 @@ function layoutLayered(scene, targets, spacing) {
3876
4370
  }
3877
4371
  const nodes = targets.filter((element) => !isLinear(element) && !isAnchored(element));
3878
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));
3879
4378
  const incoming = /* @__PURE__ */ new Map();
3880
4379
  for (const node of nodes) incoming.set(node.id, []);
3881
- for (const edge of edges) incoming.get(edge.to)?.push(edge.from);
4380
+ for (const edge of forward) incoming.get(edge.to)?.push(edge.from);
3882
4381
  const rank = new Map(nodes.map((node) => [node.id, 0]));
3883
4382
  for (let pass = 0; pass < nodes.length; pass++) {
3884
4383
  let settled = true;
3885
- for (const edge of edges) {
4384
+ for (const edge of forward) {
3886
4385
  const from = rank.get(edge.from) ?? 0;
3887
4386
  const to = rank.get(edge.to) ?? 0;
3888
4387
  if (to < from + 1) {
@@ -3923,26 +4422,87 @@ function layoutLayered(scene, targets, spacing) {
3923
4422
  previous = key;
3924
4423
  }
3925
4424
  }
3926
- const position = /* @__PURE__ */ new Map();
3927
4425
  const rankNumbers = [...slots.keys()].sort((a, b) => a - b);
4426
+ const childrenOf2 = /* @__PURE__ */ new Map();
3928
4427
  for (const r of rankNumbers) {
3929
- const bucket = slots.get(r) ?? [];
3930
- const scored = bucket.map((slot) => {
3931
- const parents = slot.parents.map((id) => position.get(id)).filter((p) => p !== void 0);
3932
- const barycentre = parents.length === 0 ? Number.POSITIVE_INFINITY : parents.reduce((sum, p) => sum + p, 0) / parents.length;
3933
- return { slot, barycentre };
3934
- });
3935
- scored.sort(
3936
- (a, b) => a.barycentre - b.barycentre || (a.slot.key < b.slot.key ? -1 : a.slot.key > b.slot.key ? 1 : 0)
3937
- );
3938
- scored.forEach(({ slot }, i) => {
3939
- position.set(slot.key, i);
3940
- });
3941
- slots.set(
3942
- r,
3943
- scored.map((entry) => entry.slot)
3944
- );
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
+ }
3945
4435
  }
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) ?? []]);
3946
4506
  const gapV = HOUSE.spacing.v;
3947
4507
  const labelWidths = labelsByContainer(scene);
3948
4508
  const captionRoom = (column) => {
@@ -3990,6 +4550,202 @@ function layoutLayered(scene, targets, spacing) {
3990
4550
  });
3991
4551
  return translate(scene, deltas);
3992
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
+ }
3993
4749
 
3994
4750
  // src/app/usecases/write/arrange-elements.ts
3995
4751
  function arrangeElements(deps) {
@@ -4165,82 +4921,6 @@ function createDebugElement(deps) {
4165
4921
  };
4166
4922
  }
4167
4923
 
4168
- // src/domain/scene/skeleton.ts
4169
- var isContainer = (type) => type === "rectangle" || type === "diamond" || type === "ellipse";
4170
- function explicit(input) {
4171
- const kept = {};
4172
- for (const [key, value] of Object.entries(input)) {
4173
- if (value === void 0) continue;
4174
- if (key === "type" || key === "id" || key === "role" || key === "label") continue;
4175
- kept[key] = value;
4176
- }
4177
- return kept;
4178
- }
4179
- var defaultSize = (element) => labelledSize(element.label, element.fontSize ?? HOUSE.fontSize);
4180
- function normalizeElements(input) {
4181
- return input.elements.map((element, i) => {
4182
- const fallback = input.ids[i];
4183
- if (fallback === void 0) {
4184
- throw new Error(`normalizeElements needs one id per element: none at position ${i}`);
4185
- }
4186
- const id = element.id ?? fallback;
4187
- const role = styleForRole(element.role ?? "compute");
4188
- const set = explicit(element);
4189
- if (element.type === "text") {
4190
- const text = element.text ?? element.label ?? "";
4191
- const skeleton2 = {
4192
- type: "text",
4193
- id,
4194
- x: element.x,
4195
- y: element.y,
4196
- fontSize: HOUSE.fontSize,
4197
- fontFamily: HOUSE.fontFamily,
4198
- // A free-standing note is not a service box: it takes the role's ink but never
4199
- // its fill, because a filled rectangle behind a caption reads as a shape.
4200
- strokeColor: role.strokeColor,
4201
- ...set,
4202
- text
4203
- };
4204
- return skeleton2;
4205
- }
4206
- if (element.type === "line") {
4207
- const skeleton2 = {
4208
- type: "line",
4209
- id,
4210
- x: element.x,
4211
- y: element.y,
4212
- strokeColor: role.strokeColor,
4213
- strokeWidth: HOUSE.strokeWidth,
4214
- roughness: HOUSE.roughness,
4215
- ...set,
4216
- ...element.label === void 0 ? {} : { label: houseLabel(element.label) }
4217
- };
4218
- return skeleton2;
4219
- }
4220
- if (!isContainer(element.type)) {
4221
- throw new Error(`normalizeElements cannot build a ${element.type}`);
4222
- }
4223
- const skeleton = {
4224
- type: element.type,
4225
- id,
4226
- x: element.x,
4227
- y: element.y,
4228
- ...defaultSize(element),
4229
- ...role,
4230
- ...set,
4231
- ...element.label === void 0 ? {} : { label: houseLabel(element.label) }
4232
- };
4233
- return skeleton;
4234
- });
4235
- }
4236
- var LAYOUT_HINT_THRESHOLD = 4;
4237
- function layoutHint(elements) {
4238
- if (elements.length < LAYOUT_HINT_THRESHOLD) return null;
4239
- const positions = new Set(elements.map((element) => `${element.x},${element.y}`));
4240
- if (positions.size > 2) return null;
4241
- return `${elements.length} elements with no explicit layout \u2014 chalk_arrange(op: "layout-layered") will place them`;
4242
- }
4243
-
4244
4924
  // src/app/usecases/write/create-elements.ts
4245
4925
  function createElements(deps) {
4246
4926
  return async (input) => {
@@ -4742,6 +5422,24 @@ function rawElements(deps) {
4742
5422
  };
4743
5423
  }
4744
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
+
4745
5443
  // src/app/usecases/write/update-elements.ts
4746
5444
  var missing = (id) => toRejection(id, unknownElement(id));
4747
5445
  var onlyRequested = (landed, requested) => {
@@ -4811,11 +5509,14 @@ function updateElements(deps) {
4811
5509
  const before = byId.get(id);
4812
5510
  const caption = captionOf(scene.value.elements, id);
4813
5511
  if (before === void 0 || caption === void 0) continue;
4814
- knockOn.push({
4815
- ...caption,
4816
- x: caption.x + (next.x - before.x),
4817
- y: caption.y + (next.y - before.y)
4818
- });
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
+ );
4819
5520
  }
4820
5521
  knockOn.push(...redrawArrowsTouching(scene.value.elements, moved));
4821
5522
  const patched = new Set(changed.map((element) => element.id));
@@ -4944,7 +5645,7 @@ function readEnv(source) {
4944
5645
  }
4945
5646
 
4946
5647
  // src/composition/container.ts
4947
- var PACKAGE_VERSION = "1.1.0";
5648
+ var PACKAGE_VERSION = "1.1.1";
4948
5649
  function readRuntime() {
4949
5650
  return {
4950
5651
  env: process.env,
@@ -5552,9 +6253,13 @@ function renderScene(description) {
5552
6253
  }
5553
6254
  lines.push("elements:");
5554
6255
  for (const element of description.elements) lines.push(row(element, true));
6256
+ lines.push(...adviceLines(description));
5555
6257
  lines.push(...overlapLines(description));
5556
6258
  return lines.join("\n");
5557
6259
  }
6260
+ function adviceLines(description) {
6261
+ return description.layoutAdvice === null ? [] : [`layout: ${description.layoutAdvice}`];
6262
+ }
5558
6263
  function overlapLines(description) {
5559
6264
  if (description.overlapCount === 0) return [];
5560
6265
  const shown = description.overlaps.length;
@@ -5958,7 +6663,7 @@ var createElementsTool = proxyTool({
5958
6663
  var connectTool = proxyTool({
5959
6664
  name: "chalk_connect",
5960
6665
  title: "Connect two elements",
5961
- 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. How the line runs, what each end looks like, and whether it is dashed all carry meaning here and are all settable \u2014 read chalk_read_me('arrows') before drawing a diagram's worth of them, or every edge comes out an identical straight solid line.",
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.",
5962
6667
  input: ConnectToolInput,
5963
6668
  output: ConnectOutput,
5964
6669
  call: (bridge) => bridge.connect
@@ -5984,7 +6689,7 @@ var deleteElementsTool = proxyTool({
5984
6689
  var arrangeTool = proxyTool({
5985
6690
  name: "chalk_arrange",
5986
6691
  title: "Arrange elements",
5987
- 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.",
5988
6693
  input: ArrangeToolInput,
5989
6694
  output: ArrangeOutput,
5990
6695
  annotations: { idempotentHint: true },