chalkbridge 1.1.0 → 1.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (152) hide show
  1. package/dist/{chunk-XDK5UUTO.js → chunk-BCHUGREF.js} +83 -6
  2. package/dist/cli.js +901 -178
  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-o3pL7yuH.js} +1 -1
  7. package/ui-dist/assets/{ar-SA-G6X2FPQ2-UCm9FftA.js → ar-SA-G6X2FPQ2-DBW5vJrl.js} +1 -1
  8. package/ui-dist/assets/architecture-7GRP2DOG-RDk7_3oa.js +1 -0
  9. package/ui-dist/assets/{architectureDiagram-5GKGNRK7-DIw1pNEk.js → architectureDiagram-5GKGNRK7-DSy67j6A.js} +1 -1
  10. package/ui-dist/assets/{az-AZ-76LH7QW2-B5LEIxkk.js → az-AZ-76LH7QW2-BmAulnA5.js} +1 -1
  11. package/ui-dist/assets/{bg-BG-XCXSNQG7-2bpThpZL.js → bg-BG-XCXSNQG7-MsfQK8qt.js} +1 -1
  12. package/ui-dist/assets/{blockDiagram-NRAW4CY4-BGr2WGp_.js → blockDiagram-NRAW4CY4-pYwdCg5K.js} +1 -1
  13. package/ui-dist/assets/{bn-BD-2XOGV67Q-Drz-y_cX.js → bn-BD-2XOGV67Q-YBAjV44K.js} +1 -1
  14. package/ui-dist/assets/{c4Diagram-UCG6FXSJ-vbm87bpC.js → c4Diagram-UCG6FXSJ-Cw8oeBvh.js} +1 -1
  15. package/ui-dist/assets/{ca-ES-6MX7JW3Y-D89p_FZ-.js → ca-ES-6MX7JW3Y-DowmBq0t.js} +1 -1
  16. package/ui-dist/assets/channel-DLSyDDT5.js +1 -0
  17. package/ui-dist/assets/{chunk-75Z2AOVW-B7kf5jVz.js → chunk-75Z2AOVW-C_9Gsbyz.js} +1 -1
  18. package/ui-dist/assets/{chunk-DU6HZSFF-BlGPS6B6.js → chunk-DU6HZSFF-CDriaWMp.js} +1 -1
  19. package/ui-dist/assets/{chunk-EIO257PC-nzXnwORk.js → chunk-EIO257PC-D_pewQb7.js} +1 -1
  20. package/ui-dist/assets/{chunk-F27PBJKO-B7qepoj_.js → chunk-F27PBJKO-Bjx929X5.js} +1 -1
  21. package/ui-dist/assets/{chunk-G27WJ6UU-CSjcvmOf.js → chunk-G27WJ6UU-CUP8Px4l.js} +1 -1
  22. package/ui-dist/assets/{chunk-GMAD6QVW-CtaZAO5u.js → chunk-GMAD6QVW-BDiTKJ44.js} +1 -1
  23. package/ui-dist/assets/{chunk-JQ64N6SF-CuhtdTse.js → chunk-JQ64N6SF-B-1BJXWR.js} +1 -1
  24. package/ui-dist/assets/{chunk-LCL6LL3I-DMfA3a_s.js → chunk-LCL6LL3I-BL1AQkvu.js} +1 -1
  25. package/ui-dist/assets/{chunk-OBVCFTLP-BoBuso2l.js → chunk-OBVCFTLP-BCWN-rmO.js} +1 -1
  26. package/ui-dist/assets/{chunk-P2QGCYS3-BZHK87sP.js → chunk-P2QGCYS3-CmgTdIFO.js} +1 -1
  27. package/ui-dist/assets/{chunk-POPQ4Y6H-Dk_v51-0.js → chunk-POPQ4Y6H-KQZYy9sO.js} +1 -1
  28. package/ui-dist/assets/{chunk-PWAF6VOD-DQTvHbxy.js → chunk-PWAF6VOD-BouCnRui.js} +1 -1
  29. package/ui-dist/assets/{chunk-R7TYR2AO-CQ_StUs1.js → chunk-R7TYR2AO-_u4RvBBs.js} +1 -1
  30. package/ui-dist/assets/{chunk-RHFEMEQ7-BTgGEQit.js → chunk-RHFEMEQ7-wg76vEGp.js} +1 -1
  31. package/ui-dist/assets/{chunk-SVP7TREG-lkVXeHxJ.js → chunk-SVP7TREG-DbwdbtHd.js} +1 -1
  32. package/ui-dist/assets/{chunk-TEH6E4GO-DIEC-0PO.js → chunk-TEH6E4GO-BzhVoidg.js} +1 -1
  33. package/ui-dist/assets/classDiagram-DTDB5LWJ-gerVLyI5.js +1 -0
  34. package/ui-dist/assets/classDiagram-v2-JRS7N3AN-gerVLyI5.js +1 -0
  35. package/ui-dist/assets/{cs-CZ-2BRQDIVT-CmSaQCAW.js → cs-CZ-2BRQDIVT-DF4u_CNK.js} +1 -1
  36. package/ui-dist/assets/{cynefin-OW5HDTMX-B6f8qooM.js → cynefin-OW5HDTMX-_c7TUx7k.js} +1 -1
  37. package/ui-dist/assets/{cynefinDiagram-5FMLGOSQ-DFu8uB2c.js → cynefinDiagram-5FMLGOSQ-Baphukwe.js} +1 -1
  38. package/ui-dist/assets/{da-DK-5WZEPLOC-DZ68cfJh.js → da-DK-5WZEPLOC-BHVp_PHH.js} +1 -1
  39. package/ui-dist/assets/{dagre-3AP2YEHR-CuUE3cEY.js → dagre-3AP2YEHR-0kiD869k.js} +1 -1
  40. package/ui-dist/assets/{de-DE-XR44H4JA-ClBUqbwZ.js → de-DE-XR44H4JA-CgW8oFzH.js} +1 -1
  41. package/ui-dist/assets/{diagram-S7CK7UJ4-DjLndaJK.js → diagram-S7CK7UJ4-zoqnxuLt.js} +1 -1
  42. package/ui-dist/assets/{diagram-UQ7AKVKN-wLhxEBwN.js → diagram-UQ7AKVKN-DMMWXR23.js} +1 -1
  43. package/ui-dist/assets/{diagram-VSXAHHWV-CVek4gvW.js → diagram-VSXAHHWV-HjDZHJFx.js} +1 -1
  44. package/ui-dist/assets/{diagram-VX7I27RA-CXyuaDuW.js → diagram-VX7I27RA-W7XiAa3Y.js} +1 -1
  45. package/ui-dist/assets/{diagram-Z3DM3KII-BGk8dw5c.js → diagram-Z3DM3KII-Ch_-xBqE.js} +1 -1
  46. package/ui-dist/assets/{dist-DyDCC61R.js → dist-BWboiH3s.js} +4 -4
  47. package/ui-dist/assets/{ebnfDiagram-PWID7BFC-Bq5wdWGX.js → ebnfDiagram-PWID7BFC-CKRspb8c.js} +1 -1
  48. package/ui-dist/assets/{el-GR-BZB4AONW-oCuJWxYE.js → el-GR-BZB4AONW-DH-i72OV.js} +1 -1
  49. package/ui-dist/assets/{en-B4ZKOASM-DevQWFhD.js → en-B4ZKOASM-BHQxRb4V.js} +1 -1
  50. package/ui-dist/assets/{erDiagram-SSCWMZ5O-CXZW4I_b.js → erDiagram-SSCWMZ5O-zX3V6vuZ.js} +1 -1
  51. package/ui-dist/assets/{es-ES-U4NZUMDT-DgzMLG1J.js → es-ES-U4NZUMDT-eUPIrX1m.js} +1 -1
  52. package/ui-dist/assets/{eu-ES-A7QVB2H4-1tA3vosZ.js → eu-ES-A7QVB2H4-Cf5MkJHT.js} +1 -1
  53. package/ui-dist/assets/eventmodeling-NTZA5JFV-DJ7hv96e.js +1 -0
  54. package/ui-dist/assets/{fa-IR-HGAKTJCU-BVbN6Ifb.js → fa-IR-HGAKTJCU-G0NL5O32.js} +1 -1
  55. package/ui-dist/assets/{fi-FI-Z5N7JZ37-DsGMdP7T.js → fi-FI-Z5N7JZ37-eDytnoCD.js} +1 -1
  56. package/ui-dist/assets/flowDiagram-A5DVABFB-B14HNvA3.js +1 -0
  57. package/ui-dist/assets/{fr-FR-RHASNOE6-BEZa94DA.js → fr-FR-RHASNOE6-CDn5YGxh.js} +1 -1
  58. package/ui-dist/assets/{ganttDiagram-EL5Y4UJY-BZxyNgRP.js → ganttDiagram-EL5Y4UJY-Cgpii2nh.js} +1 -1
  59. package/ui-dist/assets/{gitGraph-4MIJSDKK-Cz7F-_6d.js → gitGraph-4MIJSDKK-B2FLI0YT.js} +1 -1
  60. package/ui-dist/assets/{gitGraphDiagram-WWUBYQGX-77gOaAOD.js → gitGraphDiagram-WWUBYQGX-BFZVNaJk.js} +1 -1
  61. package/ui-dist/assets/{gl-ES-HMX3MZ6V-DfSDz_sy.js → gl-ES-HMX3MZ6V-DqTa_6zQ.js} +1 -1
  62. package/ui-dist/assets/{he-IL-6SHJWFNN-DT8nqxK2.js → he-IL-6SHJWFNN-CF_hmTDD.js} +1 -1
  63. package/ui-dist/assets/{hi-IN-IWLTKZ5I-BtL1eizk.js → hi-IN-IWLTKZ5I-BurR-HWO.js} +1 -1
  64. package/ui-dist/assets/{hu-HU-A5ZG7DT2-5FwjCLbT.js → hu-HU-A5ZG7DT2-CTMiHFNL.js} +1 -1
  65. package/ui-dist/assets/{id-ID-SAP4L64H-DXfUbYET.js → id-ID-SAP4L64H-v9eAe-Pl.js} +1 -1
  66. package/ui-dist/assets/image-GAAHSSAO-aYcCZOxV.js +1 -0
  67. package/ui-dist/assets/{index-Boxo043A.js → index-DyAw3r3e.js} +8 -8
  68. package/ui-dist/assets/{info-A6RAGUB7-DIe8iET8.js → info-A6RAGUB7-D9vkABZg.js} +1 -1
  69. package/ui-dist/assets/{infoDiagram-RXCK75RN-Dik-rzpk.js → infoDiagram-RXCK75RN-DkIWJAJT.js} +1 -1
  70. package/ui-dist/assets/{ishikawaDiagram-5VMMS53U-Ccb-hHW6.js → ishikawaDiagram-5VMMS53U-Bft8Dz3r.js} +1 -1
  71. package/ui-dist/assets/{it-IT-JPQ66NNP-6_sPHBVO.js → it-IT-JPQ66NNP-BBzW6blD.js} +1 -1
  72. package/ui-dist/assets/{ja-JP-DBVTYXUO-DgY_5_c8.js → ja-JP-DBVTYXUO-BTXAY95B.js} +1 -1
  73. package/ui-dist/assets/{journeyDiagram-EYS64GPL-CgzxmpyV.js → journeyDiagram-EYS64GPL-CNPbcef4.js} +1 -1
  74. package/ui-dist/assets/{kaa-6HZHGXH3-glmGx672.js → kaa-6HZHGXH3-DYouzW_m.js} +1 -1
  75. package/ui-dist/assets/{kab-KAB-ZGHBKWFO--G7baVL0.js → kab-KAB-ZGHBKWFO-DEG1xQQq.js} +1 -1
  76. package/ui-dist/assets/{kanban-definition-3QL26DDD-D6TnbrdQ.js → kanban-definition-3QL26DDD-DS4F4kyE.js} +1 -1
  77. package/ui-dist/assets/{kk-KZ-P5N5QNE5-D3Vn0qgF.js → kk-KZ-P5N5QNE5-CPOAfzr_.js} +1 -1
  78. package/ui-dist/assets/{km-KH-HSX4SM5Z-CAHt3o8M.js → km-KH-HSX4SM5Z-BO5OGKgL.js} +1 -1
  79. package/ui-dist/assets/{ko-KR-MTYHY66A-N3tCiPap.js → ko-KR-MTYHY66A-D_3EAP_r.js} +1 -1
  80. package/ui-dist/assets/{ku-TR-6OUDTVRD-CSZFwS--.js → ku-TR-6OUDTVRD-C5yHTP7U.js} +1 -1
  81. package/ui-dist/assets/{line-BhVZwKwh.js → line-BOfOxQ0L.js} +1 -1
  82. package/ui-dist/assets/{lt-LT-XHIRWOB4-DuSlkfc-.js → lt-LT-XHIRWOB4-D8sald8e.js} +1 -1
  83. package/ui-dist/assets/{lv-LV-5QDEKY6T-COuWGNLd.js → lv-LV-5QDEKY6T-nMvlSBwd.js} +1 -1
  84. package/ui-dist/assets/{mermaid-parser.core-DlqbfUOM.js → mermaid-parser.core-DbslZ7z6.js} +3 -3
  85. package/ui-dist/assets/{mindmap-definition-FBJOCRG2-D9wLjZXa.js → mindmap-definition-FBJOCRG2-CC7YRs1I.js} +1 -1
  86. package/ui-dist/assets/{mr-IN-CRQNXWMA-Bv4WHAzM.js → mr-IN-CRQNXWMA-DwYEdJ6g.js} +1 -1
  87. package/ui-dist/assets/{my-MM-5M5IBNSE-BNltBCCh.js → my-MM-5M5IBNSE-CkVtPsgu.js} +1 -1
  88. package/ui-dist/assets/{nb-NO-T6EIAALU-DgsGabVE.js → nb-NO-T6EIAALU-CfcrwCef.js} +1 -1
  89. package/ui-dist/assets/{nl-NL-IS3SIHDZ-MPMxu0cH.js → nl-NL-IS3SIHDZ-DPVjOdP8.js} +1 -1
  90. package/ui-dist/assets/{nn-NO-6E72VCQL-CY-NT6bD.js → nn-NO-6E72VCQL-z4XCBvtQ.js} +1 -1
  91. package/ui-dist/assets/{oc-FR-POXYY2M6-BCWtA2dJ.js → oc-FR-POXYY2M6-XiwDZwgM.js} +1 -1
  92. package/ui-dist/assets/{pa-IN-N4M65BXN-CU1D5Lce.js → pa-IN-N4M65BXN-DuXPS7Vb.js} +1 -1
  93. package/ui-dist/assets/{packet-AYTQ26CC-DCMIIosG.js → packet-AYTQ26CC-B4TcY0kB.js} +1 -1
  94. package/ui-dist/assets/{pegDiagram-XKGWAZYB-CpU2aj-A.js → pegDiagram-XKGWAZYB-DUSJlo42.js} +1 -1
  95. package/ui-dist/assets/percentages-BXMCSKIN-B9EK3GBV.js +1 -0
  96. package/ui-dist/assets/{pie-WAS4IAKB-BEVkH0em.js → pie-WAS4IAKB-T3VzDsca.js} +1 -1
  97. package/ui-dist/assets/{pieDiagram-E7YTZNPT-D-jyX8nJ.js → pieDiagram-E7YTZNPT-gIAK7l_u.js} +1 -1
  98. package/ui-dist/assets/{pl-PL-T2D74RX3-CwNWACCb.js → pl-PL-T2D74RX3-D00kamQC.js} +1 -1
  99. package/ui-dist/assets/{pt-BR-5N22H2LF-DCs1W5P0.js → pt-BR-5N22H2LF-C5ZNqytO.js} +1 -1
  100. package/ui-dist/assets/{pt-PT-UZXXM6DQ-Bm9Hlh9S.js → pt-PT-UZXXM6DQ-DJoIVZb6.js} +1 -1
  101. package/ui-dist/assets/{quadrantDiagram-AXDQQJYC-BqzJD4zL.js → quadrantDiagram-AXDQQJYC-QB418pxc.js} +1 -1
  102. package/ui-dist/assets/{radar-RG4KPBEZ-B6T3SZ4z.js → radar-RG4KPBEZ-BNg2Od-m.js} +1 -1
  103. package/ui-dist/assets/{railroad-74A4TZTK-BPKCaP4g.js → railroad-74A4TZTK-HxmWWcYY.js} +1 -1
  104. package/ui-dist/assets/railroad-abnf-HS5TGJTU-E1hw9v0U.js +1 -0
  105. package/ui-dist/assets/railroad-ebnf-LZEXJU2U-D11k3-Yx.js +1 -0
  106. package/ui-dist/assets/railroad-peg-WCYAUIDC-djipl3Qi.js +1 -0
  107. package/ui-dist/assets/{railroadDiagram-O6MQD6OU-CzTuUKE-.js → railroadDiagram-O6MQD6OU-BhkRr0Xw.js} +1 -1
  108. package/ui-dist/assets/{requirementDiagram-EFPCY7ZU-Bly8_reu.js → requirementDiagram-EFPCY7ZU-oFFR0XIe.js} +1 -1
  109. package/ui-dist/assets/{ro-RO-JPDTUUEW-DbKkWzmT.js → ro-RO-JPDTUUEW-BVCBG6Yb.js} +1 -1
  110. package/ui-dist/assets/{ru-RU-B4JR7IUQ-C7y8hptt.js → ru-RU-B4JR7IUQ-CL22wPes.js} +1 -1
  111. package/ui-dist/assets/{sankeyDiagram-P5KCCOFB-BNe1jsYh.js → sankeyDiagram-P5KCCOFB-Cod_IgpA.js} +1 -1
  112. package/ui-dist/assets/{sequenceDiagram-WJ2MYXX4-jpU3VDzA.js → sequenceDiagram-WJ2MYXX4-BS1ccxYk.js} +1 -1
  113. package/ui-dist/assets/{si-LK-N5RQ5JYF-DsyN9eZC.js → si-LK-N5RQ5JYF-DLaLR7Cr.js} +1 -1
  114. package/ui-dist/assets/{sk-SK-C5VTKIMK-C3GmutL2.js → sk-SK-C5VTKIMK-ZkUf5uTC.js} +1 -1
  115. package/ui-dist/assets/{sl-SI-NN7IZMDC-C6dxM6aa.js → sl-SI-NN7IZMDC-EUpD9Npa.js} +1 -1
  116. package/ui-dist/assets/{stateDiagram-HBIQ2CUA-B2ZRnydN.js → stateDiagram-HBIQ2CUA-bZ2iHCKf.js} +1 -1
  117. package/ui-dist/assets/stateDiagram-v2-4QOOHH4V-UKcZzetZ.js +1 -0
  118. package/ui-dist/assets/subset-shared.chunk-DiI1sxPG.js +1 -0
  119. package/ui-dist/assets/{subset-worker.chunk-WItGSQq_.js → subset-worker.chunk-TUbActJA.js} +1 -1
  120. package/ui-dist/assets/{sv-SE-XGPEYMSR-BPU1z_vz.js → sv-SE-XGPEYMSR-Cn1sCwWu.js} +1 -1
  121. package/ui-dist/assets/{swimlanes-XN3QIQJK-BuYIN15M.js → swimlanes-XN3QIQJK-CuBYIsLr.js} +1 -1
  122. package/ui-dist/assets/swimlanesDiagram-VK2B7HYN-E0ldUagU.js +8 -0
  123. package/ui-dist/assets/{ta-IN-2NMHFXQM-AJVbdGTO.js → ta-IN-2NMHFXQM-ChpHwna3.js} +1 -1
  124. package/ui-dist/assets/{th-TH-HPSO5L25-B1vvYNmv.js → th-TH-HPSO5L25-DmDVSHGj.js} +1 -1
  125. package/ui-dist/assets/{timeline-definition-24CTP7MA-Bld3Oj9i.js → timeline-definition-24CTP7MA-C2U3-ZAF.js} +1 -1
  126. package/ui-dist/assets/{tr-TR-DEFEU3FU-C_emJDca.js → tr-TR-DEFEU3FU-BUP3Lszu.js} +1 -1
  127. package/ui-dist/assets/{treeView-Q6P3EWNA-CQEi50Re.js → treeView-Q6P3EWNA-CySxZ2VI.js} +1 -1
  128. package/ui-dist/assets/{treemap-WGGIJYW6-D3gRwGbr.js → treemap-WGGIJYW6-D_4Wf_x5.js} +1 -1
  129. package/ui-dist/assets/{uk-UA-QMV73CPH-B69Ddo0p.js → uk-UA-QMV73CPH-BDn1-hez.js} +1 -1
  130. package/ui-dist/assets/{vennDiagram-4TSXK5OY-BWtR81LD.js → vennDiagram-4TSXK5OY-DoYDFYaB.js} +1 -1
  131. package/ui-dist/assets/{vi-VN-M7AON7JQ-ByM0OkIF.js → vi-VN-M7AON7JQ-D1JkmMTs.js} +1 -1
  132. package/ui-dist/assets/{wardley-WFR3VGLG-DpBUmo8s.js → wardley-WFR3VGLG-3NuaJoyt.js} +1 -1
  133. package/ui-dist/assets/{wardleyDiagram-VM6X3IG4-n39Dh9vZ.js → wardleyDiagram-VM6X3IG4-BkSb9uJ5.js} +1 -1
  134. package/ui-dist/assets/{xychartDiagram-S5SC5T6Z-D8-lcdGr.js → xychartDiagram-S5SC5T6Z-DaStHnZJ.js} +1 -1
  135. package/ui-dist/assets/{zh-CN-LNUGB5OW-g_S93sCp.js → zh-CN-LNUGB5OW-D6ucAnvY.js} +1 -1
  136. package/ui-dist/assets/{zh-HK-E62DVLB3-B3J_lyrF.js → zh-HK-E62DVLB3-tvTQyrAF.js} +1 -1
  137. package/ui-dist/assets/{zh-TW-RAJ6MFWO-mYLP5aqQ.js → zh-TW-RAJ6MFWO-y6am-Uwk.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-BCHUGREF.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,45 @@ 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
+ }
3990
+ function isOrthogonal(arrow) {
3991
+ return arrow.customData?.chalk?.route === "orthogonal" || arrow.elbowed === true;
3992
+ }
3993
+ function withOrthogonalMark(arrow) {
3994
+ const chalk = arrow.customData?.chalk ?? { author: "human" };
3995
+ return { customData: { ...arrow.customData, chalk: { ...chalk, route: "orthogonal" } } };
3996
+ }
3577
3997
  function redrawOne(current, labels, arrow, lane) {
3578
3998
  if (arrow.type !== "arrow") return [];
3579
3999
  const fromId = startOf(arrow);
@@ -3584,23 +4004,64 @@ function redrawOne(current, labels, arrow, lane) {
3584
4004
  if (from === void 0 || to === void 0) return [];
3585
4005
  const startGap = arrow.startBinding?.gap ?? 4;
3586
4006
  const endGap = arrow.endBinding?.gap ?? 4;
3587
- const elbowed = arrow.elbowed === true;
4007
+ const elbowed = isOrthogonal(arrow);
3588
4008
  const [sx, sy, ex, ey] = elbowed ? facePoints(boxOf(from), boxOf(to), startGap, endGap, lane) : [
3589
4009
  ...edgePoint(boxOf(from), boxOf(to), startGap),
3590
4010
  ...edgePoint(boxOf(to), boxOf(from), endGap)
3591
4011
  ];
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
- );
4012
+ const direct = elbowed ? elbowRoute(ex - sx, ey - sy) : [point(0, 0), point(ex - sx, ey - sy)];
4013
+ const obstacles = obstaclesFor(current, fromId, toId);
4014
+ const absolute = direct.map(([px2, py]) => [sx + px2, sy + py]);
4015
+ const detoured = routeHitsAny(absolute, obstacles);
4016
+ const [fsx, fsy, fex, fey] = detoured ? facePoints(boxOf(from), boxOf(to), startGap, endGap, NO_FAN) : [sx, sy, ex, ey];
4017
+ const stubStart = stubFrom(boxOf(from), [fsx, fsy]);
4018
+ const stubEnd = stubFrom(boxOf(to), [fex, fey]);
4019
+ const found = detoured ? routeAround(stubStart, stubEnd, obstacles) : null;
4020
+ const route = found === null ? null : simplify([[fsx, fsy], ...found, [fex, fey]]);
4021
+ const drawn = route ?? direct.map(([px2, py]) => [sx + px2, sy + py]);
4022
+ const settled = [...drawn];
4023
+ const secondFromStart = settled[1];
4024
+ const secondFromEnd = settled[settled.length - 2];
4025
+ if (secondFromStart !== void 0) {
4026
+ settled[0] = anchorOn(boxOf(from), secondFromStart, startGap);
4027
+ }
4028
+ if (secondFromEnd !== void 0) {
4029
+ settled[settled.length - 1] = anchorOn(boxOf(to), secondFromEnd, endGap);
4030
+ }
4031
+ const first = settled[0] ?? [sx, sy];
4032
+ const origin = [first[0], first[1]];
4033
+ const points = settled.map(([ax, ay]) => point(ax - origin[0], ay - origin[1]));
4034
+ const pxs = points.map(([px2]) => px2);
4035
+ const pys = points.map(([, py]) => py);
4036
+ const spans = {
4037
+ w: Math.max(...pxs) - Math.min(...pxs),
4038
+ h: Math.max(...pys) - Math.min(...pys)
4039
+ };
3600
4040
  const redrawn = {
3601
4041
  ...arrow,
3602
- x: sx,
3603
- y: sy,
4042
+ // Repair on contact: an arrow drawn by an earlier version carries `elbowed: true` and
4043
+ // a binding the renderer cannot use. Clearing it here means the scene heals the first
4044
+ // time anything moves under it.
4045
+ ...elbowed ? { elbowed: false, roundness: null, ...withOrthogonalMark(arrow) } : {},
4046
+ // A detour is an orthogonal route, so it is drawn as one.
4047
+ //
4048
+ // This is not cosmetic. Excalidraw owns the endpoints of a *bound* arrow at paint
4049
+ // time: with `focus: 0` it re-derives where the line meets each box from the segment
4050
+ // adjacent to it, and a detour changes exactly that segment. The stored endpoints are
4051
+ // right — all twenty-nine measured on the boundary of the box they name — and the
4052
+ // canvas still drew several of them leaving from nowhere, because the renderer had
4053
+ // recomputed them from a first segment that now pointed the wrong way. An elbow arrow
4054
+ // is anchored differently and keeps the route it was given.
4055
+ //
4056
+ // The second reason is what it looks like. A right-angled detour rendered with
4057
+ // `roundness` comes out as a long S sweeping across the diagram — which is what the
4058
+ // bend at the bottom of the sample was. Square corners are what an orthogonal route
4059
+ // actually means.
4060
+ // Square corners and the route marker — never upstream's `elbowed`, whose binding
4061
+ // contract we cannot meet and which left dragged boxes trailing detached arrows.
4062
+ ...route === null ? {} : { elbowed: false, roundness: null, ...withOrthogonalMark(arrow) },
4063
+ x: origin[0],
4064
+ y: origin[1],
3604
4065
  width: spans.w,
3605
4066
  height: spans.h,
3606
4067
  points,
@@ -3608,7 +4069,7 @@ function redrawOne(current, labels, arrow, lane) {
3608
4069
  };
3609
4070
  const label = labels.get(arrow.id);
3610
4071
  if (label === void 0) return [redrawn];
3611
- const mid = routeMidpoint({ x: sx, y: sy, points });
4072
+ const mid = routeMidpoint({ x: origin[0], y: origin[1], points });
3612
4073
  const at = { x: mid.x - label.width / 2, y: mid.y - label.height / 2 };
3613
4074
  return [redrawn, { ...label, ...at }];
3614
4075
  }
@@ -3621,6 +4082,7 @@ function faceBetween(from, to) {
3621
4082
  return dy >= 0 ? "bottom" : "top";
3622
4083
  }
3623
4084
  var LANE_SPREAD = 44;
4085
+ var LANE_INSET = 10;
3624
4086
  function laneOffsets(arrows, current) {
3625
4087
  const groups = /* @__PURE__ */ new Map();
3626
4088
  const add = (nodeId, face, node, slot) => {
@@ -3631,7 +4093,7 @@ function laneOffsets(arrows, current) {
3631
4093
  };
3632
4094
  const across = (face, box) => face === "right" || face === "left" ? centre(box)[1] : centre(box)[0];
3633
4095
  for (const arrow of arrows) {
3634
- if (arrow.type !== "arrow" || arrow.elbowed !== true) continue;
4096
+ if (arrow.type !== "arrow" || !isOrthogonal(arrow)) continue;
3635
4097
  const fromId = startOf(arrow);
3636
4098
  const toId = endOf(arrow);
3637
4099
  if (fromId === null || toId === null) continue;
@@ -3656,7 +4118,8 @@ function laneOffsets(arrows, current) {
3656
4118
  if (slots.length < 2) continue;
3657
4119
  slots.sort((a, b) => a.along - b.along || (a.arrow < b.arrow ? -1 : 1));
3658
4120
  const span = face === "right" || face === "left" ? node.height : node.width;
3659
- const spread = Math.min(LANE_SPREAD, span / (slots.length + 1));
4121
+ const usable = Math.max(0, span - LANE_INSET * 2);
4122
+ const spread = Math.min(LANE_SPREAD, usable / Math.max(1, slots.length - 1));
3660
4123
  slots.forEach((slot, i) => {
3661
4124
  const shift = (i - (slots.length - 1) / 2) * spread;
3662
4125
  const so_far = offsets.get(slot.arrow) ?? { start: 0, end: 0 };
@@ -3745,14 +4208,14 @@ var alignments = {
3745
4208
  "align-center-h": (box, bounds) => [0, bounds.y + bounds.height / 2 - (box.y + box.height / 2)],
3746
4209
  "align-center-v": (box, bounds) => [bounds.x + bounds.width / 2 - (box.x + box.width / 2), 0]
3747
4210
  };
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]);
4211
+ function distribute(boxes, spacing, axis2) {
4212
+ const size = axis2 === "x" ? "width" : "height";
4213
+ const order = boxes.map((box, i) => ({ box, i })).sort((a, b) => a.box[axis2] - b.box[axis2]);
3751
4214
  const deltas = boxes.map(() => [0, 0]);
3752
- let cursor = order[0]?.box[axis] ?? 0;
4215
+ let cursor = order[0]?.box[axis2] ?? 0;
3753
4216
  for (const { box, i } of order) {
3754
- const delta = cursor - box[axis];
3755
- deltas[i] = axis === "x" ? [delta, 0] : [0, delta];
4217
+ const delta = cursor - box[axis2];
4218
+ deltas[i] = axis2 === "x" ? [delta, 0] : [0, delta];
3756
4219
  cursor += box[size] + spacing;
3757
4220
  }
3758
4221
  return deltas;
@@ -3768,10 +4231,10 @@ function grid(boxes, options) {
3768
4231
  return [anchor.x + column * cellW - box.x, anchor.y + rowIndex * cellH - box.y];
3769
4232
  });
3770
4233
  }
3771
- function flip(boxes, axis) {
4234
+ function flip(boxes, axis2) {
3772
4235
  const bounds = boundsOfAll(boxes);
3773
4236
  return boxes.map((box) => {
3774
- if (axis === "x") {
4237
+ if (axis2 === "x") {
3775
4238
  const mirrored2 = bounds.x + bounds.width - (box.x - bounds.x) - box.width;
3776
4239
  return [mirrored2 - box.x, 0];
3777
4240
  }
@@ -3832,6 +4295,12 @@ function arrangeGeometry(input) {
3832
4295
  if (input.op === "layout-layered") {
3833
4296
  return ok({ moved: layoutLayered(input.scene, targets, input.spacing) });
3834
4297
  }
4298
+ if (input.op === "layout-radial") {
4299
+ return ok({ moved: layoutRadial(input.scene, targets, input.spacing) });
4300
+ }
4301
+ if (input.op === "layout-tree") {
4302
+ return ok({ moved: layoutTree(input.scene, targets, input.spacing) });
4303
+ }
3835
4304
  const boxes = targets.map(boxOf);
3836
4305
  const spacing = input.spacing ?? HOUSE.spacing.h;
3837
4306
  const columns = input.columns ?? Math.ceil(Math.sqrt(targets.length));
@@ -3863,6 +4332,44 @@ function arrangeGeometry(input) {
3863
4332
  });
3864
4333
  return ok({ moved: translate(input.scene, byDelta) });
3865
4334
  }
4335
+ function backEdgeIndices(nodeIds, edges) {
4336
+ const outgoing = /* @__PURE__ */ new Map();
4337
+ for (const id of nodeIds) outgoing.set(id, []);
4338
+ edges.forEach((edge, index) => {
4339
+ outgoing.get(edge.from)?.push(index);
4340
+ });
4341
+ const ON_STACK = 1;
4342
+ const DONE = 2;
4343
+ const state = /* @__PURE__ */ new Map();
4344
+ const back = /* @__PURE__ */ new Set();
4345
+ for (const root of [...nodeIds].sort()) {
4346
+ if (state.has(root)) continue;
4347
+ state.set(root, ON_STACK);
4348
+ const stack = [{ node: root, cursor: 0 }];
4349
+ while (stack.length > 0) {
4350
+ const frame = stack[stack.length - 1];
4351
+ if (frame === void 0) break;
4352
+ const fanOut = outgoing.get(frame.node) ?? [];
4353
+ if (frame.cursor >= fanOut.length) {
4354
+ state.set(frame.node, DONE);
4355
+ stack.pop();
4356
+ continue;
4357
+ }
4358
+ const index = fanOut[frame.cursor];
4359
+ frame.cursor += 1;
4360
+ if (index === void 0) continue;
4361
+ const head = edges[index]?.to;
4362
+ if (head === void 0) continue;
4363
+ const seen = state.get(head);
4364
+ if (seen === ON_STACK) back.add(index);
4365
+ else if (seen === void 0) {
4366
+ state.set(head, ON_STACK);
4367
+ stack.push({ node: head, cursor: 0 });
4368
+ }
4369
+ }
4370
+ }
4371
+ return back;
4372
+ }
3866
4373
  function layoutLayered(scene, targets, spacing) {
3867
4374
  const inSet = new Set(targets.map((element) => element.id));
3868
4375
  const edges = [];
@@ -3876,13 +4383,18 @@ function layoutLayered(scene, targets, spacing) {
3876
4383
  }
3877
4384
  const nodes = targets.filter((element) => !isLinear(element) && !isAnchored(element));
3878
4385
  if (nodes.length === 0) return [];
4386
+ const back = backEdgeIndices(
4387
+ nodes.map((node) => node.id),
4388
+ edges
4389
+ );
4390
+ const forward = edges.filter((_, index) => !back.has(index));
3879
4391
  const incoming = /* @__PURE__ */ new Map();
3880
4392
  for (const node of nodes) incoming.set(node.id, []);
3881
- for (const edge of edges) incoming.get(edge.to)?.push(edge.from);
4393
+ for (const edge of forward) incoming.get(edge.to)?.push(edge.from);
3882
4394
  const rank = new Map(nodes.map((node) => [node.id, 0]));
3883
4395
  for (let pass = 0; pass < nodes.length; pass++) {
3884
4396
  let settled = true;
3885
- for (const edge of edges) {
4397
+ for (const edge of forward) {
3886
4398
  const from = rank.get(edge.from) ?? 0;
3887
4399
  const to = rank.get(edge.to) ?? 0;
3888
4400
  if (to < from + 1) {
@@ -3923,26 +4435,87 @@ function layoutLayered(scene, targets, spacing) {
3923
4435
  previous = key;
3924
4436
  }
3925
4437
  }
3926
- const position = /* @__PURE__ */ new Map();
3927
4438
  const rankNumbers = [...slots.keys()].sort((a, b) => a - b);
4439
+ const childrenOf2 = /* @__PURE__ */ new Map();
3928
4440
  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
- );
4441
+ for (const slot of slots.get(r) ?? []) {
4442
+ for (const parent of slot.parents) {
4443
+ const kids = childrenOf2.get(parent);
4444
+ if (kids === void 0) childrenOf2.set(parent, [slot.key]);
4445
+ else kids.push(slot.key);
4446
+ }
4447
+ }
3945
4448
  }
4449
+ const indicesOf = (ordering) => {
4450
+ const at = /* @__PURE__ */ new Map();
4451
+ for (const bucket of ordering.values()) {
4452
+ bucket.forEach((slot, i) => {
4453
+ at.set(slot.key, i);
4454
+ });
4455
+ }
4456
+ return at;
4457
+ };
4458
+ const crossings = (ordering) => {
4459
+ const at = indicesOf(ordering);
4460
+ let total = 0;
4461
+ for (const r of rankNumbers) {
4462
+ const pairs = [];
4463
+ for (const slot of ordering.get(r) ?? []) {
4464
+ const below = at.get(slot.key);
4465
+ if (below === void 0) continue;
4466
+ for (const parent of slot.parents) {
4467
+ const above = at.get(parent);
4468
+ if (above !== void 0) pairs.push([above, below]);
4469
+ }
4470
+ }
4471
+ for (let i = 0; i < pairs.length; i++) {
4472
+ for (let j = i + 1; j < pairs.length; j++) {
4473
+ const a = pairs[i];
4474
+ const b = pairs[j];
4475
+ if (a === void 0 || b === void 0) continue;
4476
+ if ((a[0] - b[0]) * (a[1] - b[1]) < 0) total += 1;
4477
+ }
4478
+ }
4479
+ }
4480
+ return total;
4481
+ };
4482
+ const sweep = (ordering, direction) => {
4483
+ const at = indicesOf(ordering);
4484
+ const next = /* @__PURE__ */ new Map();
4485
+ const order = direction === "down" ? rankNumbers : [...rankNumbers].reverse();
4486
+ for (const r of order) {
4487
+ const scored = (ordering.get(r) ?? []).map((slot) => {
4488
+ const adjacent = direction === "down" ? slot.parents : childrenOf2.get(slot.key) ?? [];
4489
+ const known = adjacent.map((key) => at.get(key)).filter((index) => index !== void 0);
4490
+ return {
4491
+ slot,
4492
+ barycentre: known.length === 0 ? Number.POSITIVE_INFINITY : known.reduce((sum, index) => sum + index, 0) / known.length
4493
+ };
4494
+ });
4495
+ scored.sort(
4496
+ (a, b) => a.barycentre - b.barycentre || (a.slot.key < b.slot.key ? -1 : a.slot.key > b.slot.key ? 1 : 0)
4497
+ );
4498
+ const settled = scored.map((entry) => entry.slot);
4499
+ settled.forEach((slot, i) => {
4500
+ at.set(slot.key, i);
4501
+ });
4502
+ next.set(r, settled);
4503
+ }
4504
+ return next;
4505
+ };
4506
+ const SWEEPS = 4;
4507
+ let current = new Map(rankNumbers.map((r) => [r, [...slots.get(r) ?? []]]));
4508
+ let best = current;
4509
+ let fewest = Number.POSITIVE_INFINITY;
4510
+ for (let round = 0; round < SWEEPS; round++) {
4511
+ current = sweep(current, round % 2 === 0 ? "down" : "up");
4512
+ const score = crossings(current);
4513
+ if (score < fewest) {
4514
+ fewest = score;
4515
+ best = current;
4516
+ }
4517
+ }
4518
+ for (const r of rankNumbers) slots.set(r, [...best.get(r) ?? []]);
3946
4519
  const gapV = HOUSE.spacing.v;
3947
4520
  const labelWidths = labelsByContainer(scene);
3948
4521
  const captionRoom = (column) => {
@@ -3990,6 +4563,202 @@ function layoutLayered(scene, targets, spacing) {
3990
4563
  });
3991
4564
  return translate(scene, deltas);
3992
4565
  }
4566
+ var TAU = Math.PI * 2;
4567
+ var normalizeAngle = (radians) => (radians % TAU + TAU) % TAU;
4568
+ function meanAngle(angles) {
4569
+ let x = 0;
4570
+ let y = 0;
4571
+ for (const angle of angles) {
4572
+ x += Math.cos(angle);
4573
+ y += Math.sin(angle);
4574
+ }
4575
+ return Math.atan2(y, x);
4576
+ }
4577
+ var ringExtent = (element) => Math.max(element.width, element.height);
4578
+ function adjacency(scene, nodes) {
4579
+ const inSet = new Set(nodes.map((node) => node.id));
4580
+ const neighbours = /* @__PURE__ */ new Map();
4581
+ for (const node of nodes) neighbours.set(node.id, /* @__PURE__ */ new Set());
4582
+ for (const element of scene) {
4583
+ if (element.type !== "arrow") continue;
4584
+ const from = startOf(element);
4585
+ const to = endOf(element);
4586
+ if (from === null || to === null || from === to) continue;
4587
+ if (!inSet.has(from) || !inSet.has(to)) continue;
4588
+ neighbours.get(from)?.add(to);
4589
+ neighbours.get(to)?.add(from);
4590
+ }
4591
+ return neighbours;
4592
+ }
4593
+ function pickHub(neighbours, ordered) {
4594
+ const first = ordered[0];
4595
+ if (first === void 0) return void 0;
4596
+ const degree = (element) => neighbours.get(element.id)?.size ?? 0;
4597
+ let hub = first;
4598
+ for (const node of ordered) if (degree(node) > degree(hub)) hub = node;
4599
+ return hub;
4600
+ }
4601
+ function layoutRadial(scene, targets, spacing) {
4602
+ const nodes = targets.filter((element) => !isLinear(element) && !isAnchored(element));
4603
+ const ordered = [...nodes].sort((a, b) => a.id < b.id ? -1 : a.id > b.id ? 1 : 0);
4604
+ const neighbours = adjacency(scene, nodes);
4605
+ const hub = pickHub(neighbours, ordered);
4606
+ if (hub === void 0) return [];
4607
+ const byId = new Map(nodes.map((node) => [node.id, node]));
4608
+ const gap = spacing ?? HOUSE.spacing.h;
4609
+ const ringOf = /* @__PURE__ */ new Map([[hub.id, 0]]);
4610
+ const parentsOf = /* @__PURE__ */ new Map();
4611
+ let frontier = [hub.id];
4612
+ let depth = 0;
4613
+ while (frontier.length > 0) {
4614
+ depth += 1;
4615
+ const discovered = /* @__PURE__ */ new Map();
4616
+ for (const id of frontier) {
4617
+ for (const other of [...neighbours.get(id) ?? []].sort()) {
4618
+ if (ringOf.has(other)) continue;
4619
+ const found = discovered.get(other);
4620
+ if (found === void 0) discovered.set(other, [id]);
4621
+ else found.push(id);
4622
+ }
4623
+ }
4624
+ for (const [child, parents] of discovered) {
4625
+ ringOf.set(child, depth);
4626
+ parentsOf.set(child, parents);
4627
+ }
4628
+ frontier = [...discovered.keys()];
4629
+ }
4630
+ for (const node of ordered) {
4631
+ if (!ringOf.has(node.id)) ringOf.set(node.id, depth + 1);
4632
+ }
4633
+ const rings = /* @__PURE__ */ new Map();
4634
+ for (const node of ordered) {
4635
+ const ring = ringOf.get(node.id) ?? 0;
4636
+ const bucket = rings.get(ring);
4637
+ if (bucket === void 0) rings.set(ring, [node.id]);
4638
+ else bucket.push(node.id);
4639
+ }
4640
+ const [cx, cy] = centre(boundsOfAll(nodes.map(boxOf)));
4641
+ const deltas = /* @__PURE__ */ new Map();
4642
+ deltas.set(hub.id, [cx - (hub.x + hub.width / 2), cy - (hub.y + hub.height / 2)]);
4643
+ const angleOf = /* @__PURE__ */ new Map([[hub.id, 0]]);
4644
+ let cleared = ringExtent(hub) / 2;
4645
+ for (const ring of [...rings.keys()].filter((r) => r > 0).sort((a, b) => a - b)) {
4646
+ const members = rings.get(ring) ?? [];
4647
+ if (members.length === 0) continue;
4648
+ const scored = members.map((id) => {
4649
+ const known = (parentsOf.get(id) ?? []).map((parent) => angleOf.get(parent)).filter((angle) => angle !== void 0);
4650
+ return {
4651
+ id,
4652
+ at: known.length === 0 ? Number.POSITIVE_INFINITY : normalizeAngle(meanAngle(known))
4653
+ };
4654
+ });
4655
+ scored.sort((a, b) => a.at - b.at || (a.id < b.id ? -1 : a.id > b.id ? 1 : 0));
4656
+ const extents = members.map((id) => {
4657
+ const node = byId.get(id);
4658
+ return node === void 0 ? 0 : ringExtent(node);
4659
+ });
4660
+ const widest = Math.max(...extents);
4661
+ const seating = extents.reduce((sum, extent) => sum + extent + gap, 0) / TAU;
4662
+ const radius = Math.max(seating, cleared + widest / 2 + gap);
4663
+ scored.forEach(({ id }, i) => {
4664
+ const theta = -Math.PI / 2 + TAU * i / members.length;
4665
+ angleOf.set(id, normalizeAngle(theta));
4666
+ const node = byId.get(id);
4667
+ if (node === void 0) return;
4668
+ deltas.set(id, [
4669
+ cx + radius * Math.cos(theta) - node.width / 2 - node.x,
4670
+ cy + radius * Math.sin(theta) - node.height / 2 - node.y
4671
+ ]);
4672
+ });
4673
+ cleared = radius + widest / 2;
4674
+ }
4675
+ return translate(scene, deltas);
4676
+ }
4677
+ function layoutTree(scene, targets, spacing) {
4678
+ const nodes = targets.filter((element) => !isLinear(element) && !isAnchored(element));
4679
+ const ordered = [...nodes].sort((a, b) => a.id < b.id ? -1 : a.id > b.id ? 1 : 0);
4680
+ const neighbours = adjacency(scene, nodes);
4681
+ const root = pickHub(neighbours, ordered);
4682
+ if (root === void 0) return [];
4683
+ const byId = new Map(nodes.map((node) => [node.id, node]));
4684
+ const gapH = spacing ?? HOUSE.spacing.h;
4685
+ const gapV = HOUSE.spacing.v;
4686
+ const depthOf = /* @__PURE__ */ new Map([[root.id, 0]]);
4687
+ const childrenOf2 = /* @__PURE__ */ new Map();
4688
+ const order = [];
4689
+ const stack = [root.id];
4690
+ const seen = /* @__PURE__ */ new Set([root.id]);
4691
+ while (stack.length > 0) {
4692
+ const id = stack.pop();
4693
+ if (id === void 0) continue;
4694
+ order.push(id);
4695
+ const kids = [];
4696
+ const adjacent = [...neighbours.get(id) ?? []].sort();
4697
+ for (const other of adjacent) {
4698
+ if (seen.has(other)) continue;
4699
+ seen.add(other);
4700
+ depthOf.set(other, (depthOf.get(id) ?? 0) + 1);
4701
+ kids.push(other);
4702
+ }
4703
+ childrenOf2.set(id, kids);
4704
+ for (let i = kids.length - 1; i >= 0; i--) {
4705
+ const kid = kids[i];
4706
+ if (kid !== void 0) stack.push(kid);
4707
+ }
4708
+ }
4709
+ for (const node of ordered) {
4710
+ if (seen.has(node.id)) continue;
4711
+ seen.add(node.id);
4712
+ depthOf.set(node.id, 1);
4713
+ order.push(node.id);
4714
+ }
4715
+ const columns = /* @__PURE__ */ new Map();
4716
+ for (const id of order) {
4717
+ const d = depthOf.get(id) ?? 0;
4718
+ const bucket = columns.get(d);
4719
+ if (bucket === void 0) columns.set(d, [id]);
4720
+ else bucket.push(id);
4721
+ }
4722
+ const captions = labelsByContainer(scene);
4723
+ const crossing = /* @__PURE__ */ new Map();
4724
+ for (const element of scene) {
4725
+ if (element.type !== "arrow") continue;
4726
+ const from = startOf(element);
4727
+ const to = endOf(element);
4728
+ if (from === null || to === null) continue;
4729
+ const a = depthOf.get(from);
4730
+ const b = depthOf.get(to);
4731
+ if (a === void 0 || b === void 0 || a === b) continue;
4732
+ const caption = captions.get(element.id);
4733
+ if (caption === void 0) continue;
4734
+ const gap = Math.min(a, b);
4735
+ crossing.set(gap, Math.max(crossing.get(gap) ?? 0, caption.width));
4736
+ }
4737
+ const anchor = boundsOfAll(nodes.map(boxOf));
4738
+ const depths = [...columns.keys()].sort((a, b) => a - b);
4739
+ const columnX = /* @__PURE__ */ new Map();
4740
+ let cursorX = anchor.x;
4741
+ for (const d of depths) {
4742
+ columnX.set(d, cursorX);
4743
+ const widest = Math.max(...(columns.get(d) ?? []).map((id) => byId.get(id)?.width ?? 0));
4744
+ const caption = crossing.get(d) ?? 0;
4745
+ cursorX += widest + Math.max(gapH, caption === 0 ? 0 : caption + HOUSE.sizes.labelPadX * 2);
4746
+ }
4747
+ const columnHeight = (d) => (columns.get(d) ?? []).reduce((sum, id) => sum + (byId.get(id)?.height ?? 0) + gapV, -gapV);
4748
+ const tallest = Math.max(...depths.map(columnHeight));
4749
+ const deltas = /* @__PURE__ */ new Map();
4750
+ for (const d of depths) {
4751
+ let cursorY = anchor.y + (tallest - columnHeight(d)) / 2;
4752
+ for (const id of columns.get(d) ?? []) {
4753
+ const node = byId.get(id);
4754
+ if (node === void 0) continue;
4755
+ const x = columnX.get(d) ?? anchor.x;
4756
+ deltas.set(id, [x - node.x, cursorY - node.y]);
4757
+ cursorY += node.height + gapV;
4758
+ }
4759
+ }
4760
+ return translate(scene, deltas);
4761
+ }
3993
4762
 
3994
4763
  // src/app/usecases/write/arrange-elements.ts
3995
4764
  function arrangeElements(deps) {
@@ -4031,7 +4800,7 @@ function shapeOf(arrowType) {
4031
4800
  case "sharp":
4032
4801
  return { roundness: null, elbowed: false };
4033
4802
  case "elbow":
4034
- return { roundness: null, elbowed: true };
4803
+ return { roundness: null, elbowed: false, orthogonal: true };
4035
4804
  default:
4036
4805
  return { roundness: { type: ROUNDNESS.PROPORTIONAL_RADIUS }, elbowed: false };
4037
4806
  }
@@ -4047,7 +4816,7 @@ function connectElements(deps) {
4047
4816
  const to = live.get(input.toId);
4048
4817
  if (from === void 0) return fail(unknownElement(input.fromId));
4049
4818
  if (to === void 0) return fail(unknownElement(input.toId));
4050
- const shape = shapeOf(input.arrowType);
4819
+ const { orthogonal, ...shape } = shapeOf(input.arrowType);
4051
4820
  const skeleton = {
4052
4821
  type: "arrow",
4053
4822
  id: deps.idGen.elementId(),
@@ -4060,6 +4829,11 @@ function connectElements(deps) {
4060
4829
  strokeWidth: HOUSE.strokeWidth,
4061
4830
  roughness: HOUSE.roughness,
4062
4831
  ...shape,
4832
+ // Right angles are recorded under `customData.chalk`, which is where Chalkbridge
4833
+ // metadata lives (invariant 23), rather than as upstream's `elbowed` — that flag
4834
+ // narrows both bindings to `FixedPointBinding` and an arrow carrying it without one
4835
+ // does not follow its box when the box is dragged. `ChalkMeta.route` has the detail.
4836
+ ...orthogonal === true ? { customData: { chalk: { author: "claude", route: "orthogonal" } } } : {},
4063
4837
  ...input.strokeStyle === void 0 ? {} : { strokeStyle: input.strokeStyle },
4064
4838
  ...input.startArrowhead === void 0 ? {} : { startArrowhead: input.startArrowhead },
4065
4839
  ...input.endArrowhead === void 0 ? {} : { endArrowhead: input.endArrowhead },
@@ -4165,82 +4939,6 @@ function createDebugElement(deps) {
4165
4939
  };
4166
4940
  }
4167
4941
 
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
4942
  // src/app/usecases/write/create-elements.ts
4245
4943
  function createElements(deps) {
4246
4944
  return async (input) => {
@@ -4742,6 +5440,24 @@ function rawElements(deps) {
4742
5440
  };
4743
5441
  }
4744
5442
 
5443
+ // src/domain/scene/captions.ts
5444
+ function fitCaption(container, caption) {
5445
+ if (caption.type !== "text") return caption;
5446
+ const source = caption.originalText === "" ? caption.text : caption.originalText;
5447
+ const inner = Math.max(1, container.width - HOUSE.sizes.labelPadX * 2);
5448
+ const lines = wrapLines(source, caption.fontSize, inner);
5449
+ const width = Math.max(...lines.map((line) => measureLine(line, caption.fontSize)));
5450
+ const height = lines.length * caption.fontSize * caption.lineHeight;
5451
+ return {
5452
+ ...caption,
5453
+ text: lines.join("\n"),
5454
+ width,
5455
+ height,
5456
+ x: container.x + (container.width - width) / 2,
5457
+ y: container.y + (container.height - height) / 2
5458
+ };
5459
+ }
5460
+
4745
5461
  // src/app/usecases/write/update-elements.ts
4746
5462
  var missing = (id) => toRejection(id, unknownElement(id));
4747
5463
  var onlyRequested = (landed, requested) => {
@@ -4811,11 +5527,14 @@ function updateElements(deps) {
4811
5527
  const before = byId.get(id);
4812
5528
  const caption = captionOf(scene.value.elements, id);
4813
5529
  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
- });
5530
+ const resized = next.width !== before.width || next.height !== before.height;
5531
+ knockOn.push(
5532
+ resized ? fitCaption(next, caption) : {
5533
+ ...caption,
5534
+ x: caption.x + (next.x - before.x),
5535
+ y: caption.y + (next.y - before.y)
5536
+ }
5537
+ );
4819
5538
  }
4820
5539
  knockOn.push(...redrawArrowsTouching(scene.value.elements, moved));
4821
5540
  const patched = new Set(changed.map((element) => element.id));
@@ -4944,7 +5663,7 @@ function readEnv(source) {
4944
5663
  }
4945
5664
 
4946
5665
  // src/composition/container.ts
4947
- var PACKAGE_VERSION = "1.1.0";
5666
+ var PACKAGE_VERSION = "1.1.2";
4948
5667
  function readRuntime() {
4949
5668
  return {
4950
5669
  env: process.env,
@@ -5552,9 +6271,13 @@ function renderScene(description) {
5552
6271
  }
5553
6272
  lines.push("elements:");
5554
6273
  for (const element of description.elements) lines.push(row(element, true));
6274
+ lines.push(...adviceLines(description));
5555
6275
  lines.push(...overlapLines(description));
5556
6276
  return lines.join("\n");
5557
6277
  }
6278
+ function adviceLines(description) {
6279
+ return description.layoutAdvice === null ? [] : [`layout: ${description.layoutAdvice}`];
6280
+ }
5558
6281
  function overlapLines(description) {
5559
6282
  if (description.overlapCount === 0) return [];
5560
6283
  const shown = description.overlaps.length;
@@ -5958,7 +6681,7 @@ var createElementsTool = proxyTool({
5958
6681
  var connectTool = proxyTool({
5959
6682
  name: "chalk_connect",
5960
6683
  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.",
6684
+ 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
6685
  input: ConnectToolInput,
5963
6686
  output: ConnectOutput,
5964
6687
  call: (bridge) => bridge.connect
@@ -5984,7 +6707,7 @@ var deleteElementsTool = proxyTool({
5984
6707
  var arrangeTool = proxyTool({
5985
6708
  name: "chalk_arrange",
5986
6709
  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.",
6710
+ 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
6711
  input: ArrangeToolInput,
5989
6712
  output: ArrangeOutput,
5990
6713
  annotations: { idempotentHint: true },