@trohde/agentic-canvas 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (147) hide show
  1. package/CHANGELOG.md +13 -1
  2. package/README.md +10 -7
  3. package/dist/cli/index.js +660 -52
  4. package/dist/cli/index.js.map +1 -1
  5. package/dist/web/assets/{ar-SA-G6X2FPQ2-Bx_JGCzc.js → ar-SA-G6X2FPQ2-Cm_aowk8.js} +1 -1
  6. package/dist/web/assets/{arc-CZEYm-X2.js → arc-BfEOcH9b.js} +1 -1
  7. package/dist/web/assets/architecture-7EHR7CIX-D9uaHVVA.js +1 -0
  8. package/dist/web/assets/{architectureDiagram-3BPJPVTR-BX4UqKAe.js → architectureDiagram-3BPJPVTR-D6EV9nh8.js} +1 -1
  9. package/dist/web/assets/{az-AZ-76LH7QW2-CQnzCEm_.js → az-AZ-76LH7QW2-BKDm9AO9.js} +1 -1
  10. package/dist/web/assets/{bg-BG-XCXSNQG7-B6OhJNg1.js → bg-BG-XCXSNQG7-Bhj2ixUd.js} +1 -1
  11. package/dist/web/assets/{blockDiagram-GPEHLZMM-DOaLZNH2.js → blockDiagram-GPEHLZMM-uuVOs3ER.js} +1 -1
  12. package/dist/web/assets/{bn-BD-2XOGV67Q-CL8DmeD-.js → bn-BD-2XOGV67Q-CnDq7W6e.js} +1 -1
  13. package/dist/web/assets/{c4Diagram-AAUBKEIU-BOYbD4o9.js → c4Diagram-AAUBKEIU-CyjNcZVo.js} +1 -1
  14. package/dist/web/assets/{ca-ES-6MX7JW3Y-DqXuGb3N.js → ca-ES-6MX7JW3Y-CzFlQ5Bd.js} +1 -1
  15. package/dist/web/assets/channel-DcqTbL2I.js +1 -0
  16. package/dist/web/assets/{chunk-2J33WTMH-9pRDhFmL.js → chunk-2J33WTMH-DJms322I.js} +1 -1
  17. package/dist/web/assets/{chunk-3OPIFGDE-Cxtej-eZ.js → chunk-3OPIFGDE-DDQRiaw9.js} +1 -1
  18. package/dist/web/assets/{chunk-4BX2VUAB-WGnRPgB4.js → chunk-4BX2VUAB-8fkcN1qh.js} +1 -1
  19. package/dist/web/assets/{chunk-55IACEB6-By23tM0L.js → chunk-55IACEB6-C_yQ14T3.js} +1 -1
  20. package/dist/web/assets/{chunk-5ZQYHXKU-BbvpmZDT.js → chunk-5ZQYHXKU-ZNEPgpb3.js} +1 -1
  21. package/dist/web/assets/{chunk-727SXJPM-DeN-6nx2.js → chunk-727SXJPM-DH8nxAzX.js} +1 -1
  22. package/dist/web/assets/{chunk-AQP2D5EJ-3vlT2osY.js → chunk-AQP2D5EJ-DfLHvN4q.js} +1 -1
  23. package/dist/web/assets/{chunk-BSJP7CBP-ggZwEsYs.js → chunk-BSJP7CBP-Cf2UEop4.js} +1 -1
  24. package/dist/web/assets/{chunk-CSCIHK7Q-CkCt7BGB.js → chunk-CSCIHK7Q-Bt5Wp92e.js} +1 -1
  25. package/dist/web/assets/{chunk-EIO257PC-DhAXVvAc.js → chunk-EIO257PC-CzqCX7ih.js} +1 -1
  26. package/dist/web/assets/{chunk-FMBD7UC4-BWW1dpup.js → chunk-FMBD7UC4-Dn7yJ4Og.js} +1 -1
  27. package/dist/web/assets/{chunk-KSCS5N6A-ByGpruIk.js → chunk-KSCS5N6A-Ckb-EIkQ.js} +1 -1
  28. package/dist/web/assets/{chunk-L5ZTLDWV-Brj903IS.js → chunk-L5ZTLDWV-DMOYXaNY.js} +1 -1
  29. package/dist/web/assets/{chunk-LZXEDZCA-B0E6SbHa.js → chunk-LZXEDZCA-BZJbFie6.js} +2 -2
  30. package/dist/web/assets/{chunk-ND2GUHAM-cgO1vAyy.js → chunk-ND2GUHAM-CH3bRn4I.js} +1 -1
  31. package/dist/web/assets/{chunk-NZK2D7GU-rJf6zpwf.js → chunk-NZK2D7GU-I17AXEJG.js} +1 -1
  32. package/dist/web/assets/{chunk-O5CBEL6O-DZpUP7B-.js → chunk-O5CBEL6O-hgdRiomR.js} +1 -1
  33. package/dist/web/assets/chunk-QZHKN3VN-DsIBQWIa.js +1 -0
  34. package/dist/web/assets/chunk-WU5MYG2G-CvtGRUhe.js +1 -0
  35. package/dist/web/assets/{chunk-XPW4576I-DRG_5uRX.js → chunk-XPW4576I-9ec1XdXl.js} +1 -1
  36. package/dist/web/assets/classDiagram-4FO5ZUOK-BnjqOVph.js +1 -0
  37. package/dist/web/assets/classDiagram-v2-Q7XG4LA2-BnjqOVph.js +1 -0
  38. package/dist/web/assets/{cose-bilkent-S5V4N54A-Dzdtq3ax.js → cose-bilkent-S5V4N54A-CTAH7-Na.js} +1 -1
  39. package/dist/web/assets/{cs-CZ-2BRQDIVT-C9Hi60ft.js → cs-CZ-2BRQDIVT-BqCGlckY.js} +1 -1
  40. package/dist/web/assets/{da-DK-5WZEPLOC-DuscK2dU.js → da-DK-5WZEPLOC-CQW71ILY.js} +1 -1
  41. package/dist/web/assets/{dagre-BM42HDAG-DG-1WRTH.js → dagre-BM42HDAG-jBLuWLOm.js} +1 -1
  42. package/dist/web/assets/{de-DE-XR44H4JA-BUknVi9m.js → de-DE-XR44H4JA-BGJ_QF0p.js} +1 -1
  43. package/dist/web/assets/{diagram-2AECGRRQ-C01yy19V.js → diagram-2AECGRRQ-Bdk2bzIQ.js} +1 -1
  44. package/dist/web/assets/{diagram-5GNKFQAL-9Ly54gPf.js → diagram-5GNKFQAL-BtXDpnfX.js} +1 -1
  45. package/dist/web/assets/{diagram-KO2AKTUF-Bnzn_Fk2.js → diagram-KO2AKTUF-Dzpy-rDd.js} +1 -1
  46. package/dist/web/assets/{diagram-LMA3HP47-CkQJH7qS.js → diagram-LMA3HP47-DpyU3oY9.js} +1 -1
  47. package/dist/web/assets/{diagram-OG6HWLK6-sYTSt9S6.js → diagram-OG6HWLK6-D_vZYBEy.js} +1 -1
  48. package/dist/web/assets/{dist-CHteZrRU.js → dist-D2xqQQPY.js} +1 -1
  49. package/dist/web/assets/{dist--k9jX8ko.js → dist-DbhSHjHs.js} +4 -4
  50. package/dist/web/assets/{el-GR-BZB4AONW-CBx8IJCe.js → el-GR-BZB4AONW-CtK8iWZk.js} +1 -1
  51. package/dist/web/assets/{en-B4ZKOASM-D04FpeSQ.js → en-B4ZKOASM-QIuF9S_L.js} +1 -1
  52. package/dist/web/assets/{erDiagram-TEJ5UH35-kBTcPhaQ.js → erDiagram-TEJ5UH35-CPSP5rtu.js} +1 -1
  53. package/dist/web/assets/{es-ES-U4NZUMDT-nQPOkQIb.js → es-ES-U4NZUMDT-BGRuwoLW.js} +1 -1
  54. package/dist/web/assets/{eu-ES-A7QVB2H4-OVL5j0Bz.js → eu-ES-A7QVB2H4-BffwZaTm.js} +1 -1
  55. package/dist/web/assets/eventmodeling-FCH6USID-DsUO1N_d.js +1 -0
  56. package/dist/web/assets/{fa-IR-HGAKTJCU-BXJbxYFk.js → fa-IR-HGAKTJCU-DPSCuyoW.js} +1 -1
  57. package/dist/web/assets/{fi-FI-Z5N7JZ37-DoZfr4yv.js → fi-FI-Z5N7JZ37-8dRu4kif.js} +1 -1
  58. package/dist/web/assets/{flowDiagram-I6XJVG4X-CwQyslgb.js → flowDiagram-I6XJVG4X-pB7KUsSZ.js} +1 -1
  59. package/dist/web/assets/{fr-FR-RHASNOE6-CXLv0m_p.js → fr-FR-RHASNOE6-C8e_ndFO.js} +1 -1
  60. package/dist/web/assets/{ganttDiagram-6RSMTGT7-B-1gME9q.js → ganttDiagram-6RSMTGT7-DEwygfiX.js} +1 -1
  61. package/dist/web/assets/{gitGraph-WXDBUCRP-BC3bsb5A.js → gitGraph-WXDBUCRP-DyH-gphX.js} +1 -1
  62. package/dist/web/assets/{gitGraphDiagram-PVQCEYII-DUh_E2g-.js → gitGraphDiagram-PVQCEYII-C580aOqP.js} +1 -1
  63. package/dist/web/assets/{gl-ES-HMX3MZ6V-DMYtQjyy.js → gl-ES-HMX3MZ6V-DZvyfyMN.js} +1 -1
  64. package/dist/web/assets/{he-IL-6SHJWFNN-BXviMgxV.js → he-IL-6SHJWFNN-DdcdKhtV.js} +1 -1
  65. package/dist/web/assets/{hi-IN-IWLTKZ5I-B6s0F0bv.js → hi-IN-IWLTKZ5I-BluyCJ3K.js} +1 -1
  66. package/dist/web/assets/{hu-HU-A5ZG7DT2-DZY88thP.js → hu-HU-A5ZG7DT2-Dg3oGdTy.js} +1 -1
  67. package/dist/web/assets/{id-ID-SAP4L64H-CsRPE_UY.js → id-ID-SAP4L64H-BLUaAQdX.js} +1 -1
  68. package/dist/web/assets/image-GAAHSSAO-Du_312iO.js +1 -0
  69. package/dist/web/assets/{index-Do_WCY2x.js → index-CaeNa0Nd.js} +8 -8
  70. package/dist/web/assets/{info-J43DQDTF-DgcAwO7L.js → info-J43DQDTF-CT6GvwIw.js} +1 -1
  71. package/dist/web/assets/{infoDiagram-5YYISTIA-BVoRZtf6.js → infoDiagram-5YYISTIA-DajsEaol.js} +1 -1
  72. package/dist/web/assets/{ishikawaDiagram-YF4QCWOH-Pc0lSZjn.js → ishikawaDiagram-YF4QCWOH-B4l6Aaj1.js} +1 -1
  73. package/dist/web/assets/{it-IT-JPQ66NNP-D6QIIUKx.js → it-IT-JPQ66NNP-CjKMpPiP.js} +1 -1
  74. package/dist/web/assets/{ja-JP-DBVTYXUO-BKIOpiiN.js → ja-JP-DBVTYXUO-7hTYylEN.js} +1 -1
  75. package/dist/web/assets/{journeyDiagram-JHISSGLW-CUTIkP_3.js → journeyDiagram-JHISSGLW-BYZtl1Hi.js} +1 -1
  76. package/dist/web/assets/{kaa-6HZHGXH3-Ck2PqClI.js → kaa-6HZHGXH3-DZbKTMJH.js} +1 -1
  77. package/dist/web/assets/{kab-KAB-ZGHBKWFO-9067fQ1h.js → kab-KAB-ZGHBKWFO-CfiL0AkF.js} +1 -1
  78. package/dist/web/assets/{kanban-definition-UN3LZRKU-f9h-Kl6S.js → kanban-definition-UN3LZRKU-3KTfHyGo.js} +1 -1
  79. package/dist/web/assets/{kk-KZ-P5N5QNE5-Ds5pD5Rs.js → kk-KZ-P5N5QNE5-CL1X8K-V.js} +1 -1
  80. package/dist/web/assets/{km-KH-HSX4SM5Z-BvgABkMn.js → km-KH-HSX4SM5Z-DkiDM0om.js} +1 -1
  81. package/dist/web/assets/{ko-KR-MTYHY66A-BI-VJ_qS.js → ko-KR-MTYHY66A-DeAZ7jFA.js} +1 -1
  82. package/dist/web/assets/{ku-TR-6OUDTVRD-CzO0QxpT.js → ku-TR-6OUDTVRD-BOybWoIi.js} +1 -1
  83. package/dist/web/assets/{line-BM7n-WSY.js → line-BVaj9bp2.js} +1 -1
  84. package/dist/web/assets/{linear-CoV0e-iv.js → linear-BbEWsyY4.js} +1 -1
  85. package/dist/web/assets/{lt-LT-XHIRWOB4-Wdr8437y.js → lt-LT-XHIRWOB4-C5H7N0uM.js} +1 -1
  86. package/dist/web/assets/{lv-LV-5QDEKY6T-Baqs6ETz.js → lv-LV-5QDEKY6T-T9e_HCTF.js} +1 -1
  87. package/dist/web/assets/{mermaid-parser.core-BxU7L1C-.js → mermaid-parser.core-CBlXvD0u.js} +2 -2
  88. package/dist/web/assets/{mindmap-definition-RKZ34NQL-e2CkjxCV.js → mindmap-definition-RKZ34NQL-DQbTUk5-.js} +1 -1
  89. package/dist/web/assets/{mr-IN-CRQNXWMA-_P3j2iZu.js → mr-IN-CRQNXWMA-B-PthEKF.js} +1 -1
  90. package/dist/web/assets/{my-MM-5M5IBNSE-u-YXjqOx.js → my-MM-5M5IBNSE-BvaKSQfh.js} +1 -1
  91. package/dist/web/assets/{nb-NO-T6EIAALU-BVTkGOfM.js → nb-NO-T6EIAALU-AVukMlbc.js} +1 -1
  92. package/dist/web/assets/{nl-NL-IS3SIHDZ-Dz45KRHt.js → nl-NL-IS3SIHDZ-1Ais5hls.js} +1 -1
  93. package/dist/web/assets/{nn-NO-6E72VCQL-Bv1T99Os.js → nn-NO-6E72VCQL-CMMf615t.js} +1 -1
  94. package/dist/web/assets/{oc-FR-POXYY2M6-CWPx0BPy.js → oc-FR-POXYY2M6-COfJfqJD.js} +1 -1
  95. package/dist/web/assets/{pa-IN-N4M65BXN-CUAufnLD.js → pa-IN-N4M65BXN-BMA3pTXK.js} +1 -1
  96. package/dist/web/assets/{packet-YPE3B663-DSUdqwD6.js → packet-YPE3B663-BBkze78i.js} +1 -1
  97. package/dist/web/assets/percentages-BXMCSKIN-D6JI391u.js +1 -0
  98. package/dist/web/assets/{pica-lghYzniR.js → pica-0kffCCbc.js} +1 -1
  99. package/dist/web/assets/{pie-LRSECV5Y-CtaLKxkL.js → pie-LRSECV5Y-CKSNEbTP.js} +1 -1
  100. package/dist/web/assets/{pieDiagram-4H26LBE5-KQxiMI7y.js → pieDiagram-4H26LBE5-BBbcOiMW.js} +1 -1
  101. package/dist/web/assets/{pl-PL-T2D74RX3-BYDGKTrw.js → pl-PL-T2D74RX3-BynL7lcx.js} +1 -1
  102. package/dist/web/assets/{pt-BR-5N22H2LF-DiLHA1Fv.js → pt-BR-5N22H2LF-C4hPdgZm.js} +1 -1
  103. package/dist/web/assets/{pt-PT-UZXXM6DQ-DO4nqrjh.js → pt-PT-UZXXM6DQ-CohDul9s.js} +1 -1
  104. package/dist/web/assets/{quadrantDiagram-W4KKPZXB-DYnhMBii.js → quadrantDiagram-W4KKPZXB-DbFZ4mVF.js} +1 -1
  105. package/dist/web/assets/{radar-GUYGQ44K-BWVL_5jd.js → radar-GUYGQ44K-nUUcbbyf.js} +1 -1
  106. package/dist/web/assets/{requirementDiagram-4Y6WPE33-CLSyCDmb.js → requirementDiagram-4Y6WPE33-BE4DIJ6B.js} +1 -1
  107. package/dist/web/assets/{ro-RO-JPDTUUEW-CkArAo01.js → ro-RO-JPDTUUEW-NPZ6H5_k.js} +1 -1
  108. package/dist/web/assets/{ru-RU-B4JR7IUQ-CF7qZR2L.js → ru-RU-B4JR7IUQ-DUYJexox.js} +1 -1
  109. package/dist/web/assets/{sankeyDiagram-5OEKKPKP-fCrXqVCu.js → sankeyDiagram-5OEKKPKP-BBgQr7tO.js} +1 -1
  110. package/dist/web/assets/{sequenceDiagram-3UESZ5HK-Ibg1UsPm.js → sequenceDiagram-3UESZ5HK-Bve3KCrM.js} +1 -1
  111. package/dist/web/assets/{si-LK-N5RQ5JYF-SlyZ_B_5.js → si-LK-N5RQ5JYF-YQIyxfwu.js} +1 -1
  112. package/dist/web/assets/{sk-SK-C5VTKIMK-B59JZXsV.js → sk-SK-C5VTKIMK-naC01XZT.js} +1 -1
  113. package/dist/web/assets/{sl-SI-NN7IZMDC-CkVIpRnH.js → sl-SI-NN7IZMDC-c8M-xB_J.js} +1 -1
  114. package/dist/web/assets/{src-Dc-yDLup.js → src-5GYS8Ljo.js} +1 -1
  115. package/dist/web/assets/{stateDiagram-AJRCARHV-Cg6ervwo.js → stateDiagram-AJRCARHV-ooB93unN.js} +1 -1
  116. package/dist/web/assets/stateDiagram-v2-BHNVJYJU-CQKTJr2T.js +1 -0
  117. package/dist/web/assets/subset-shared.chunk-Lb-_zEfd.js +1 -0
  118. package/dist/web/assets/{subset-worker.chunk-DN4FBbFb.js → subset-worker.chunk-Cm3Hfued.js} +1 -1
  119. package/dist/web/assets/{sv-SE-XGPEYMSR-BwnaFVSC.js → sv-SE-XGPEYMSR-BxQJQlZm.js} +1 -1
  120. package/dist/web/assets/{ta-IN-2NMHFXQM-Digwj7d-.js → ta-IN-2NMHFXQM-CLstIlbu.js} +1 -1
  121. package/dist/web/assets/{th-TH-HPSO5L25-Ck3FgDpQ.js → th-TH-HPSO5L25-C1Fw6Lrv.js} +1 -1
  122. package/dist/web/assets/{timeline-definition-PNZ67QCA-Sxfm8qnw.js → timeline-definition-PNZ67QCA-DSwgdcbw.js} +1 -1
  123. package/dist/web/assets/{tr-TR-DEFEU3FU-CZntbglt.js → tr-TR-DEFEU3FU-yIjSKsYZ.js} +1 -1
  124. package/dist/web/assets/{treeView-BLDUP644-Cx8t5kj4.js → treeView-BLDUP644-qRIad0RL.js} +1 -1
  125. package/dist/web/assets/{treemap-LRROVOQU-Dfh_IQDp.js → treemap-LRROVOQU-DkzmeI5b.js} +1 -1
  126. package/dist/web/assets/{uk-UA-QMV73CPH-B0wjKowt.js → uk-UA-QMV73CPH-Cxu2wJjR.js} +1 -1
  127. package/dist/web/assets/{vennDiagram-CIIHVFJN-BMEOJ0gl.js → vennDiagram-CIIHVFJN-C9IbTAeZ.js} +1 -1
  128. package/dist/web/assets/{vi-VN-M7AON7JQ-Dj0BUYxm.js → vi-VN-M7AON7JQ-CVlx5WSz.js} +1 -1
  129. package/dist/web/assets/{wardley-L42UT6IY-EJTnFNq7.js → wardley-L42UT6IY-BoetX5iL.js} +1 -1
  130. package/dist/web/assets/{wardleyDiagram-YWT4CUSO-BJjdV56L.js → wardleyDiagram-YWT4CUSO-BnSF5czb.js} +1 -1
  131. package/dist/web/assets/{xychartDiagram-2RQKCTM6-CBQ88pV0.js → xychartDiagram-2RQKCTM6-CnGeJixh.js} +1 -1
  132. package/dist/web/assets/{zh-CN-LNUGB5OW-K1_YaWy1.js → zh-CN-LNUGB5OW-Cnl39PI9.js} +1 -1
  133. package/dist/web/assets/{zh-HK-E62DVLB3-DCU_gXiJ.js → zh-HK-E62DVLB3-D0hO_t0Q.js} +1 -1
  134. package/dist/web/assets/{zh-TW-RAJ6MFWO-1ApXhCfP.js → zh-TW-RAJ6MFWO-Cq71LAZa.js} +1 -1
  135. package/dist/web/index.html +1 -1
  136. package/package.json +1 -1
  137. package/dist/web/assets/architecture-7EHR7CIX-BW08P3jh.js +0 -1
  138. package/dist/web/assets/channel-jlcmNkDM.js +0 -1
  139. package/dist/web/assets/chunk-QZHKN3VN-B2hsWqvV.js +0 -1
  140. package/dist/web/assets/chunk-WU5MYG2G-ad-_f9bA.js +0 -1
  141. package/dist/web/assets/classDiagram-4FO5ZUOK-pgvxJ5K2.js +0 -1
  142. package/dist/web/assets/classDiagram-v2-Q7XG4LA2-pgvxJ5K2.js +0 -1
  143. package/dist/web/assets/eventmodeling-FCH6USID-3Z5bJ280.js +0 -1
  144. package/dist/web/assets/image-GAAHSSAO-Ufj_572K.js +0 -1
  145. package/dist/web/assets/percentages-BXMCSKIN-BRi-lUYV.js +0 -1
  146. package/dist/web/assets/stateDiagram-v2-BHNVJYJU-Z9e_4KHc.js +0 -1
  147. package/dist/web/assets/subset-shared.chunk-CnjPFGnW.js +0 -1
package/dist/cli/index.js CHANGED
@@ -20,12 +20,65 @@ function isElementEndpoint(endpoint) {
20
20
  }
21
21
  function cloneScene(scene) {
22
22
  return {
23
- elements: scene.elements.map((element) => ({ ...element })),
23
+ elements: scene.elements.map(cloneElement),
24
24
  appState: { ...scene.appState },
25
25
  files: { ...scene.files },
26
26
  version: scene.version
27
27
  };
28
28
  }
29
+ function cloneElement(element) {
30
+ return {
31
+ ...element,
32
+ groupIds: [...element.groupIds],
33
+ roundness: element.roundness ? { ...element.roundness } : null,
34
+ boundElements: element.boundElements ? element.boundElements.map((bound) => ({ ...bound })) : null,
35
+ points: element.points ? element.points.map(([x, y]) => [x, y]) : void 0,
36
+ startBinding: element.startBinding ? { ...element.startBinding } : element.startBinding,
37
+ endBinding: element.endBinding ? { ...element.endBinding } : element.endBinding,
38
+ lastCommittedPoint: element.lastCommittedPoint ? [element.lastCommittedPoint[0], element.lastCommittedPoint[1]] : element.lastCommittedPoint,
39
+ customData: element.customData ? { ...element.customData } : element.customData
40
+ };
41
+ }
42
+
43
+ // src/shared/colors.ts
44
+ var NAMED_COLORS = /* @__PURE__ */ new Set([
45
+ "black",
46
+ "white",
47
+ "red",
48
+ "green",
49
+ "blue",
50
+ "yellow",
51
+ "orange",
52
+ "purple",
53
+ "pink",
54
+ "brown",
55
+ "gray",
56
+ "grey",
57
+ "cyan",
58
+ "magenta",
59
+ "lime",
60
+ "navy",
61
+ "teal",
62
+ "transparent"
63
+ ]);
64
+ function isCanvasColor(value) {
65
+ const color = value.trim().toLowerCase();
66
+ if (NAMED_COLORS.has(color)) {
67
+ return true;
68
+ }
69
+ if (/^#(?:[0-9a-f]{3}|[0-9a-f]{4}|[0-9a-f]{6}|[0-9a-f]{8})$/i.test(color)) {
70
+ return true;
71
+ }
72
+ if (/^rgba?\(\s*\d{1,3}\s*,\s*\d{1,3}\s*,\s*\d{1,3}(?:\s*,\s*(?:0|1|0?\.\d+))?\s*\)$/i.test(color)) {
73
+ return true;
74
+ }
75
+ if (/^hsla?\(\s*-?\d+(?:deg)?\s*,\s*\d{1,3}%\s*,\s*\d{1,3}%(?:\s*,\s*(?:0|1|0?\.\d+))?\s*\)$/i.test(
76
+ color
77
+ )) {
78
+ return true;
79
+ }
80
+ return false;
81
+ }
29
82
 
30
83
  // src/plugins/excalidraw/adapter.ts
31
84
  var SUPPORTED_TYPES = /* @__PURE__ */ new Set([
@@ -106,6 +159,52 @@ function normalizeRoughness(roughness) {
106
159
 
107
160
  // src/plugins/excalidraw/elements.ts
108
161
  import { randomUUID } from "crypto";
162
+
163
+ // src/plugins/excalidraw/textMetrics.ts
164
+ var DEFAULT_FONT_SIZE = 20;
165
+ var EXCALIDRAW_LINE_HEIGHT = 1.25;
166
+ function measureTextBounds(text, style) {
167
+ const fontSize = style?.fontSize ?? DEFAULT_FONT_SIZE;
168
+ const lineHeight = fontSize * EXCALIDRAW_LINE_HEIGHT;
169
+ const lines = splitLines(text);
170
+ const widestLine = Math.max(...lines.map((line) => measureLineWidth(line, fontSize)), 0);
171
+ return {
172
+ width: Math.max(40, Math.ceil(widestLine)),
173
+ height: Math.max(lineHeight, Math.ceil(lines.length * lineHeight)),
174
+ fontSize,
175
+ lineHeight: EXCALIDRAW_LINE_HEIGHT
176
+ };
177
+ }
178
+ function splitLines(text) {
179
+ return text.split(/\r\n|\r|\n/);
180
+ }
181
+ function measureLineWidth(line, fontSize) {
182
+ let width = 0;
183
+ for (const character of line) {
184
+ width += fontSize * characterWeight(character);
185
+ }
186
+ return width;
187
+ }
188
+ function characterWeight(character) {
189
+ if (character === " " || character === " ") {
190
+ return 0.35;
191
+ }
192
+ if (/^[ilI.,:;|!']$/.test(character)) {
193
+ return 0.32;
194
+ }
195
+ if (/^[mwMW@#%&]$/.test(character)) {
196
+ return 0.9;
197
+ }
198
+ if (/^[A-Z0-9]$/.test(character)) {
199
+ return 0.68;
200
+ }
201
+ if ((character.codePointAt(0) ?? 0) > 11904) {
202
+ return 1;
203
+ }
204
+ return 0.58;
205
+ }
206
+
207
+ // src/plugins/excalidraw/elements.ts
109
208
  var DEFAULT_WIDTH = 160;
110
209
  var DEFAULT_HEIGHT = 80;
111
210
  var ORDER_KEY_DIGITS = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
@@ -144,18 +243,20 @@ function buildElement(spec, options = {}) {
144
243
  locked: false
145
244
  };
146
245
  if (type === "text") {
246
+ const text = options.text ?? spec.text ?? "";
247
+ const bounds = measureTextBounds(text, spec.style);
147
248
  return {
148
249
  ...base,
149
- width: spec.width ?? Math.max(40, (options.text ?? spec.text ?? "").length * 10),
150
- height: spec.height ?? spec.style?.fontSize ?? 24,
151
- text: options.text ?? spec.text ?? "",
152
- originalText: options.text ?? spec.text ?? "",
153
- fontSize: spec.style?.fontSize ?? 20,
250
+ width: spec.width ?? bounds.width,
251
+ height: spec.height ?? bounds.height,
252
+ text,
253
+ originalText: text,
254
+ fontSize: bounds.fontSize,
154
255
  fontFamily: 1,
155
256
  textAlign: spec.style?.textAlign ?? "center",
156
257
  verticalAlign: "middle",
157
258
  containerId: options.containerId ?? spec.containerId ?? null,
158
- lineHeight: 1.25,
259
+ lineHeight: bounds.lineHeight,
159
260
  autoResize: true
160
261
  };
161
262
  }
@@ -339,9 +440,12 @@ var canvasObjectTypeSchema = z.enum([
339
440
  "text",
340
441
  "frame"
341
442
  ]);
443
+ var colorSchema = z.string().refine(isCanvasColor, {
444
+ message: "Invalid canvas color"
445
+ });
342
446
  var styleSchema = z.object({
343
- strokeColor: z.string().optional(),
344
- backgroundColor: z.string().optional(),
447
+ strokeColor: colorSchema.optional(),
448
+ backgroundColor: colorSchema.optional(),
345
449
  fillStyle: z.enum(["hachure", "cross-hatch", "solid"]).optional(),
346
450
  strokeWidth: z.union([z.literal(1), z.literal(2), z.literal(4)]).optional(),
347
451
  strokeStyle: z.enum(["solid", "dashed", "dotted"]).optional(),
@@ -355,13 +459,13 @@ var endpointSchema = z.union([
355
459
  z.object({ x: z.number(), y: z.number() })
356
460
  ]);
357
461
  var pointSchema = z.array(z.number()).length(2);
358
- var pointsSchema = z.array(pointSchema);
462
+ var pointsSchema = z.array(pointSchema).min(2);
359
463
  var createObjectShape = {
360
464
  type: canvasObjectTypeSchema,
361
465
  x: z.number(),
362
466
  y: z.number(),
363
- width: z.number().optional(),
364
- height: z.number().optional(),
467
+ width: z.number().positive().optional(),
468
+ height: z.number().positive().optional(),
365
469
  text: z.string().optional(),
366
470
  points: pointsSchema.optional(),
367
471
  style: styleSchema.optional(),
@@ -374,8 +478,8 @@ var updateObjectShape = {
374
478
  id: z.string(),
375
479
  x: z.number().optional(),
376
480
  y: z.number().optional(),
377
- width: z.number().optional(),
378
- height: z.number().optional(),
481
+ width: z.number().positive().optional(),
482
+ height: z.number().positive().optional(),
379
483
  text: z.string().optional(),
380
484
  points: pointsSchema.optional(),
381
485
  style: styleSchema.optional(),
@@ -387,6 +491,7 @@ var updateObjectShape = {
387
491
 
388
492
  // src/plugins/excalidraw/flowchart.ts
389
493
  function planFlowchart(input) {
494
+ validateFlowchart(input);
390
495
  const direction = input.direction ?? "LR";
391
496
  const spacingX = input.spacingX ?? 220;
392
497
  const spacingY = input.spacingY ?? 140;
@@ -419,9 +524,10 @@ function planFlowchart(input) {
419
524
  }
420
525
  function assignLevels(input) {
421
526
  const nodeIds = new Set(input.nodes.map((node) => node.id));
527
+ const backEdges = findBackEdges(input);
422
528
  const indegrees = new Map(input.nodes.map((node) => [node.id, 0]));
423
529
  for (const edge of input.edges) {
424
- if (nodeIds.has(edge.to)) {
530
+ if (nodeIds.has(edge.to) && !backEdges.has(edgeKey(edge))) {
425
531
  indegrees.set(edge.to, (indegrees.get(edge.to) ?? 0) + 1);
426
532
  }
427
533
  }
@@ -433,6 +539,9 @@ function assignLevels(input) {
433
539
  for (let pass = 0; pass < input.nodes.length; pass += 1) {
434
540
  let changed = false;
435
541
  for (const edge of input.edges) {
542
+ if (backEdges.has(edgeKey(edge))) {
543
+ continue;
544
+ }
436
545
  const fromLevel = levels.get(edge.from);
437
546
  if (fromLevel === void 0 || !nodeIds.has(edge.to)) {
438
547
  continue;
@@ -465,14 +574,62 @@ function groupLevels(nodes, levels) {
465
574
  function peerOffset(index, count, spacing) {
466
575
  return (index - (count - 1) / 2) * spacing;
467
576
  }
577
+ function validateFlowchart(input) {
578
+ const nodeIds = /* @__PURE__ */ new Set();
579
+ for (const node of input.nodes) {
580
+ if (nodeIds.has(node.id)) {
581
+ throw new Error(`Duplicate flowchart node id: ${node.id}`);
582
+ }
583
+ nodeIds.add(node.id);
584
+ }
585
+ for (const edge of input.edges) {
586
+ if (!nodeIds.has(edge.from) || !nodeIds.has(edge.to)) {
587
+ throw new Error(`Flowchart edge references missing node: ${edge.from} -> ${edge.to}`);
588
+ }
589
+ }
590
+ }
591
+ function findBackEdges(input) {
592
+ const adjacency = /* @__PURE__ */ new Map();
593
+ for (const edge of input.edges) {
594
+ adjacency.set(edge.from, [...adjacency.get(edge.from) ?? [], edge]);
595
+ }
596
+ const visited = /* @__PURE__ */ new Set();
597
+ const visiting = /* @__PURE__ */ new Set();
598
+ const backEdges = /* @__PURE__ */ new Set();
599
+ const visit = (nodeId) => {
600
+ if (visiting.has(nodeId)) {
601
+ return;
602
+ }
603
+ if (visited.has(nodeId)) {
604
+ return;
605
+ }
606
+ visiting.add(nodeId);
607
+ for (const edge of adjacency.get(nodeId) ?? []) {
608
+ if (visiting.has(edge.to)) {
609
+ backEdges.add(edgeKey(edge));
610
+ continue;
611
+ }
612
+ visit(edge.to);
613
+ }
614
+ visiting.delete(nodeId);
615
+ visited.add(nodeId);
616
+ };
617
+ for (const node of input.nodes) {
618
+ visit(node.id);
619
+ }
620
+ return backEdges;
621
+ }
622
+ function edgeKey(edge) {
623
+ return `${edge.from}\0${edge.to}`;
624
+ }
468
625
 
469
626
  // src/plugins/excalidraw/tools.ts
470
627
  var shapeInput = {
471
628
  x: z2.number(),
472
629
  y: z2.number(),
473
- width: z2.number(),
474
- height: z2.number(),
475
- text: z2.string().optional(),
630
+ width: z2.number().positive(),
631
+ height: z2.number().positive(),
632
+ text: z2.string().min(1).optional(),
476
633
  style: styleSchema.optional()
477
634
  };
478
635
  function registerExcalidrawTools(server, context) {
@@ -538,7 +695,7 @@ function registerExcalidrawTools(server, context) {
538
695
  inputSchema: {
539
696
  x: z2.number().optional(),
540
697
  y: z2.number().optional(),
541
- text: z2.string(),
698
+ text: z2.string().min(1),
542
699
  containerId: z2.string().optional(),
543
700
  style: styleSchema.optional()
544
701
  }
@@ -567,9 +724,9 @@ function registerExcalidrawTools(server, context) {
567
724
  inputSchema: {
568
725
  x: z2.number(),
569
726
  y: z2.number(),
570
- width: z2.number(),
571
- height: z2.number(),
572
- name: z2.string().optional(),
727
+ width: z2.number().positive(),
728
+ height: z2.number().positive(),
729
+ name: z2.string().min(1).optional(),
573
730
  childIds: z2.array(z2.string()).optional()
574
731
  }
575
732
  },
@@ -595,13 +752,51 @@ function registerExcalidrawTools(server, context) {
595
752
  "group_objects",
596
753
  {
597
754
  description: "Group existing Excalidraw elements.",
755
+ inputSchema: {
756
+ ids: z2.array(z2.string()).min(2)
757
+ }
758
+ },
759
+ async ({ ids }) => {
760
+ try {
761
+ return textResult(context.controller.mutateScene((scene) => groupElements(scene, ids)));
762
+ } catch (error) {
763
+ return errorResult(error);
764
+ }
765
+ }
766
+ );
767
+ server.registerTool(
768
+ "ungroup_objects",
769
+ {
770
+ description: "Remove one group, or all groups, from existing Excalidraw elements.",
771
+ inputSchema: {
772
+ ids: z2.array(z2.string()).min(1),
773
+ groupId: z2.string().optional()
774
+ }
775
+ },
776
+ async ({ ids, groupId }) => {
777
+ try {
778
+ return textResult(
779
+ context.controller.mutateScene((scene) => ungroupElements(scene, ids, groupId))
780
+ );
781
+ } catch (error) {
782
+ return errorResult(error);
783
+ }
784
+ }
785
+ );
786
+ server.registerTool(
787
+ "remove_from_frame",
788
+ {
789
+ description: "Clear the frame assignment from existing Excalidraw elements.",
598
790
  inputSchema: {
599
791
  ids: z2.array(z2.string()).min(1)
600
792
  }
601
793
  },
602
794
  async ({ ids }) => {
603
- const groupId = context.controller.mutateScene((scene) => groupElements(scene, ids));
604
- return textResult({ groupId });
795
+ try {
796
+ return textResult(context.controller.mutateScene((scene) => removeFromFrame(scene, ids)));
797
+ } catch (error) {
798
+ return errorResult(error);
799
+ }
605
800
  }
606
801
  );
607
802
  server.registerTool(
@@ -612,14 +807,14 @@ function registerExcalidrawTools(server, context) {
612
807
  nodes: z2.array(
613
808
  z2.object({
614
809
  id: z2.string(),
615
- label: z2.string(),
810
+ label: z2.string().min(1),
616
811
  shape: z2.enum(["rectangle", "ellipse", "diamond"]).optional(),
617
812
  x: z2.number().optional(),
618
813
  y: z2.number().optional()
619
814
  })
620
815
  ).min(1),
621
816
  edges: z2.array(
622
- z2.object({ from: z2.string(), to: z2.string(), label: z2.string().optional() })
817
+ z2.object({ from: z2.string(), to: z2.string(), label: z2.string().min(1).optional() })
623
818
  ),
624
819
  direction: z2.enum(["TB", "LR"]).optional(),
625
820
  spacingX: z2.number().positive().optional(),
@@ -730,8 +925,15 @@ function getObject(scene, id) {
730
925
  return element ? toCanvasObject(element) : void 0;
731
926
  }
732
927
  function createObject(scene, spec) {
928
+ validateCreateSpec(spec);
929
+ if (spec.containerId && !findElement(scene, spec.containerId)) {
930
+ throw new Error(`Object not found: ${spec.containerId}`);
931
+ }
733
932
  const element = buildElementWithBindings(scene, spec);
734
933
  scene.elements.push(element);
934
+ if (spec.type === "text" && spec.containerId) {
935
+ addBoundElementById(scene, spec.containerId, { id: element.id, type: "text" });
936
+ }
735
937
  if (spec.text && canCreateBoundLabel(spec.type)) {
736
938
  const label = createBoundLabel(element, spec.text, spec.style);
737
939
  scene.elements.push(label);
@@ -747,6 +949,7 @@ function updateObject(scene, id, patch) {
747
949
  if (!element) {
748
950
  return void 0;
749
951
  }
952
+ validateUpdatePatch(element, patch);
750
953
  if (patch.x !== void 0) {
751
954
  element.x = patch.x;
752
955
  }
@@ -772,25 +975,54 @@ function updateObject(scene, id, patch) {
772
975
  }
773
976
  if (patch.style) {
774
977
  applyStyle(element, patch.style);
978
+ if (element.type === "text") {
979
+ resizeTextElement(element);
980
+ } else {
981
+ applyTextStyleToBoundLabels(scene, element, patch.style);
982
+ }
775
983
  }
776
984
  if (patch.text !== void 0) {
777
985
  if (element.type === "text") {
778
986
  element.text = patch.text;
779
987
  element.originalText = patch.text;
988
+ resizeTextElement(element);
989
+ if (element.containerId) {
990
+ const container = findElement(scene, element.containerId);
991
+ if (container) {
992
+ relayoutBoundLabels(scene, container);
993
+ }
994
+ }
780
995
  } else {
781
996
  upsertContainerLabel(scene, element, patch.text, patch.style);
782
997
  }
783
998
  }
784
999
  touchElement(element);
1000
+ syncBoundElements(scene, element);
785
1001
  return toCanvasObject(element);
786
1002
  }
787
1003
  function deleteObjects(scene, ids) {
788
1004
  const idSet = new Set(ids);
789
- const before = scene.elements.length;
790
- scene.elements = scene.elements.filter(
791
- (element) => !idSet.has(element.id) && !idSet.has(element.containerId ?? "")
792
- );
793
- return before === scene.elements.length ? [] : ids.filter((id) => !findElement(scene, id));
1005
+ const deleted = /* @__PURE__ */ new Set();
1006
+ for (const element of scene.elements) {
1007
+ if (idSet.has(element.id) || idSet.has(element.containerId ?? "")) {
1008
+ deleted.add(element.id);
1009
+ }
1010
+ }
1011
+ if (deleted.size === 0) {
1012
+ return [];
1013
+ }
1014
+ scene.elements = scene.elements.filter((element) => !deleted.has(element.id));
1015
+ for (const element of scene.elements) {
1016
+ if (!element.boundElements) {
1017
+ continue;
1018
+ }
1019
+ const kept = element.boundElements.filter((bound) => !deleted.has(bound.id));
1020
+ if (kept.length !== element.boundElements.length) {
1021
+ element.boundElements = kept.length > 0 ? kept : null;
1022
+ touchElement(element);
1023
+ }
1024
+ }
1025
+ return [...deleted];
794
1026
  }
795
1027
  function clear(scene) {
796
1028
  scene.elements = [];
@@ -869,6 +1101,7 @@ function upsertContainerLabel(scene, container, text, style) {
869
1101
  if (style) {
870
1102
  applyStyle(existing, style);
871
1103
  }
1104
+ positionBoundLabel(existing, container);
872
1105
  touchElement(existing);
873
1106
  return;
874
1107
  }
@@ -896,24 +1129,21 @@ function createBoundLabel(container, text, style) {
896
1129
  return label;
897
1130
  }
898
1131
  function labelPosition(container, text, style) {
899
- const fontSize = style.fontSize ?? 20;
900
- const lineHeight = fontSize * 1.25;
1132
+ const measured = measureTextBounds(text, style);
901
1133
  if (isLinearElement(container)) {
902
1134
  const midpoint = linearMidpoint(container);
903
- const width = Math.max(40, text.length * fontSize * 0.6);
904
- const height = lineHeight;
905
1135
  return {
906
- x: midpoint.x - width / 2,
907
- y: midpoint.y - height / 2,
908
- width,
909
- height
1136
+ x: midpoint.x - measured.width / 2,
1137
+ y: midpoint.y - measured.height / 2,
1138
+ width: measured.width,
1139
+ height: measured.height
910
1140
  };
911
1141
  }
912
1142
  return {
913
1143
  x: container.x,
914
- y: container.y + container.height / 2 - lineHeight / 2,
1144
+ y: container.y + container.height / 2 - measured.height / 2,
915
1145
  width: Math.max(40, container.width),
916
- height: lineHeight
1146
+ height: measured.height
917
1147
  };
918
1148
  }
919
1149
  function isLinearElement(element) {
@@ -956,15 +1186,39 @@ function setFrameOnChildren(scene, childIds, frameId) {
956
1186
  return updated;
957
1187
  }
958
1188
  function groupElements(scene, ids) {
1189
+ const elements = resolveElements(scene, ids);
1190
+ if (elements.length < 2) {
1191
+ throw new Error("At least two existing objects are required to create a group");
1192
+ }
959
1193
  const groupId = randomUUID2();
960
- for (const id of ids) {
961
- const element = findElement(scene, id);
962
- if (element && !element.groupIds.includes(groupId)) {
1194
+ for (const element of elements) {
1195
+ if (!element.groupIds.includes(groupId)) {
963
1196
  element.groupIds = [...element.groupIds, groupId];
964
1197
  touchElement(element);
965
1198
  }
966
1199
  }
967
- return groupId;
1200
+ return { groupId, ids: elements.map((element) => element.id) };
1201
+ }
1202
+ function ungroupElements(scene, ids, groupId) {
1203
+ const elements = resolveElements(scene, ids);
1204
+ for (const element of elements) {
1205
+ const nextGroupIds = groupId ? element.groupIds.filter((candidate) => candidate !== groupId) : [];
1206
+ if (nextGroupIds.length !== element.groupIds.length) {
1207
+ element.groupIds = nextGroupIds;
1208
+ touchElement(element);
1209
+ }
1210
+ }
1211
+ return { ids: elements.map((element) => element.id), groupId };
1212
+ }
1213
+ function removeFromFrame(scene, ids) {
1214
+ const elements = resolveElements(scene, ids);
1215
+ for (const element of elements) {
1216
+ if (element.frameId !== null) {
1217
+ element.frameId = null;
1218
+ touchElement(element);
1219
+ }
1220
+ }
1221
+ return { ids: elements.map((element) => element.id) };
968
1222
  }
969
1223
  function applyStyle(element, style) {
970
1224
  if (style.strokeColor !== void 0) {
@@ -989,13 +1243,184 @@ function applyStyle(element, style) {
989
1243
  element.opacity = style.opacity;
990
1244
  }
991
1245
  if (style.fontSize !== void 0) {
1246
+ if (element.type !== "text") {
1247
+ return;
1248
+ }
992
1249
  element.fontSize = style.fontSize;
993
- element.height = style.fontSize * 1.25;
994
1250
  }
995
1251
  if (style.textAlign !== void 0) {
1252
+ if (element.type !== "text") {
1253
+ return;
1254
+ }
996
1255
  element.textAlign = style.textAlign;
997
1256
  }
998
1257
  }
1258
+ function validateCreateSpec(spec) {
1259
+ validateStyle(spec.style);
1260
+ validateText(spec.type, spec.text);
1261
+ validateDimensions(spec.type, spec.width, spec.height);
1262
+ if ((spec.type === "line" || spec.type === "arrow") && spec.points) {
1263
+ validateLinearPoints(spec.points);
1264
+ }
1265
+ }
1266
+ function validateUpdatePatch(element, patch) {
1267
+ validateStyle(patch.style);
1268
+ validateText(element.type, patch.text);
1269
+ validateDimensions(element.type, patch.width, patch.height);
1270
+ if (patch.points) {
1271
+ validateLinearPoints(patch.points);
1272
+ }
1273
+ }
1274
+ function validateStyle(style) {
1275
+ for (const [field, value] of [
1276
+ ["strokeColor", style?.strokeColor],
1277
+ ["backgroundColor", style?.backgroundColor]
1278
+ ]) {
1279
+ if (value !== void 0 && !isCanvasColor(value)) {
1280
+ throw new Error(`Invalid ${field}: ${value}`);
1281
+ }
1282
+ }
1283
+ }
1284
+ function validateText(type, text) {
1285
+ if (text !== void 0 && text.trim().length === 0 && type !== "frame") {
1286
+ throw new Error("Text must not be empty");
1287
+ }
1288
+ }
1289
+ function validateDimensions(type, width, height) {
1290
+ if (type === "line" || type === "arrow" || type === "text") {
1291
+ if (width !== void 0 && height !== void 0 && width === 0 && height === 0) {
1292
+ throw new Error("Linear geometry must not be zero length");
1293
+ }
1294
+ return;
1295
+ }
1296
+ if (width !== void 0 && width <= 0) {
1297
+ throw new Error("Width must be greater than zero");
1298
+ }
1299
+ if (height !== void 0 && height <= 0) {
1300
+ throw new Error("Height must be greater than zero");
1301
+ }
1302
+ }
1303
+ function validateLinearPoints(points) {
1304
+ if (points.length < 2) {
1305
+ throw new Error("Line and arrow points must contain at least two points");
1306
+ }
1307
+ const [firstX, firstY] = points[0] ?? [0, 0];
1308
+ if (points.every(([x, y]) => x === firstX && y === firstY)) {
1309
+ throw new Error("Line and arrow points must not be zero length");
1310
+ }
1311
+ }
1312
+ function resizeTextElement(element) {
1313
+ const bounds = measureTextBounds(element.text ?? "", styleFromElement(element));
1314
+ element.width = bounds.width;
1315
+ element.height = bounds.height;
1316
+ element.fontSize = bounds.fontSize;
1317
+ element.lineHeight = bounds.lineHeight;
1318
+ }
1319
+ function syncBoundElements(scene, element) {
1320
+ relayoutBoundLabels(scene, element);
1321
+ rerouteBoundArrows(scene, element);
1322
+ }
1323
+ function relayoutBoundLabels(scene, container) {
1324
+ for (const label of scene.elements.filter(
1325
+ (candidate) => candidate.type === "text" && candidate.containerId === container.id
1326
+ )) {
1327
+ positionBoundLabel(label, container);
1328
+ touchElement(label);
1329
+ }
1330
+ }
1331
+ function applyTextStyleToBoundLabels(scene, container, style) {
1332
+ if (style.fontSize === void 0 && style.textAlign === void 0) {
1333
+ return;
1334
+ }
1335
+ for (const label of scene.elements.filter(
1336
+ (candidate) => candidate.type === "text" && candidate.containerId === container.id
1337
+ )) {
1338
+ applyStyle(label, {
1339
+ fontSize: style.fontSize,
1340
+ textAlign: style.textAlign
1341
+ });
1342
+ resizeTextElement(label);
1343
+ positionBoundLabel(label, container);
1344
+ touchElement(label);
1345
+ }
1346
+ }
1347
+ function positionBoundLabel(label, container) {
1348
+ const position = labelPosition(container, label.text ?? "", styleFromElement(label));
1349
+ label.x = position.x;
1350
+ label.y = position.y;
1351
+ label.width = position.width;
1352
+ label.height = position.height;
1353
+ label.autoResize = false;
1354
+ }
1355
+ function rerouteBoundArrows(scene, changedElement) {
1356
+ const arrowIds = new Set(
1357
+ (changedElement.boundElements ?? []).filter((bound) => bound.type === "arrow").map((bound) => bound.id)
1358
+ );
1359
+ for (const candidate of scene.elements) {
1360
+ if (isLinearElement(candidate) && (candidate.startBinding?.elementId === changedElement.id || candidate.endBinding?.elementId === changedElement.id)) {
1361
+ arrowIds.add(candidate.id);
1362
+ }
1363
+ }
1364
+ for (const arrowId of arrowIds) {
1365
+ const arrow = findElement(scene, arrowId);
1366
+ if (!arrow || !isLinearElement(arrow)) {
1367
+ continue;
1368
+ }
1369
+ rerouteArrow(scene, arrow);
1370
+ relayoutBoundLabels(scene, arrow);
1371
+ }
1372
+ }
1373
+ function rerouteArrow(scene, arrow) {
1374
+ const startTarget = arrow.startBinding ? findElement(scene, arrow.startBinding.elementId) : void 0;
1375
+ const endTarget = arrow.endBinding ? findElement(scene, arrow.endBinding.elementId) : void 0;
1376
+ if (!startTarget && !endTarget) {
1377
+ return;
1378
+ }
1379
+ const currentStart = absoluteStartPoint(arrow);
1380
+ const currentEnd = absoluteEndPoint(arrow);
1381
+ const startReference = endTarget ? centerPoint(endTarget) : currentEnd;
1382
+ const endReference = startTarget ? centerPoint(startTarget) : currentStart;
1383
+ const startPoint = startTarget ? edgePoint(startTarget, startReference.x, startReference.y) : currentStart;
1384
+ const endPoint = endTarget ? edgePoint(endTarget, endReference.x, endReference.y) : currentEnd;
1385
+ arrow.x = startPoint.x;
1386
+ arrow.y = startPoint.y;
1387
+ arrow.points = [
1388
+ [0, 0],
1389
+ [endPoint.x - startPoint.x, endPoint.y - startPoint.y]
1390
+ ];
1391
+ arrow.width = linearWidth2(arrow.points);
1392
+ arrow.height = linearHeight2(arrow.points);
1393
+ touchElement(arrow);
1394
+ }
1395
+ function absoluteStartPoint(element) {
1396
+ const first = element.points?.[0] ?? [0, 0];
1397
+ return { x: element.x + first[0], y: element.y + first[1] };
1398
+ }
1399
+ function absoluteEndPoint(element) {
1400
+ const points = element.points ?? [[0, 0]];
1401
+ const last = points[points.length - 1] ?? [0, 0];
1402
+ return { x: element.x + last[0], y: element.y + last[1] };
1403
+ }
1404
+ function styleFromElement(element) {
1405
+ return {
1406
+ strokeColor: element.strokeColor,
1407
+ backgroundColor: element.backgroundColor,
1408
+ fillStyle: element.fillStyle,
1409
+ strokeWidth: element.strokeWidth,
1410
+ strokeStyle: element.strokeStyle,
1411
+ roughness: element.roughness,
1412
+ opacity: element.opacity,
1413
+ fontSize: element.fontSize,
1414
+ textAlign: element.textAlign
1415
+ };
1416
+ }
1417
+ function resolveElements(scene, ids) {
1418
+ const missingIds = ids.filter((id) => !findElement(scene, id));
1419
+ if (missingIds.length > 0) {
1420
+ throw new Error(`Object not found: ${missingIds.join(", ")}`);
1421
+ }
1422
+ return ids.map((id) => findElement(scene, id)).filter((element) => Boolean(element));
1423
+ }
999
1424
  function linearWidth2(points) {
1000
1425
  const xs = points.map(([x]) => x);
1001
1426
  return Math.max(...xs) - Math.min(...xs);
@@ -1132,6 +1557,21 @@ function registerBaselineTools(server, context) {
1132
1557
  }
1133
1558
  }
1134
1559
  );
1560
+ server.registerTool(
1561
+ "set_canvas_background",
1562
+ {
1563
+ description: "Set the canvas background color.",
1564
+ inputSchema: {
1565
+ color: colorSchema
1566
+ }
1567
+ },
1568
+ async ({ color }) => {
1569
+ context.controller.mutateScene((scene) => {
1570
+ scene.appState.viewBackgroundColor = color;
1571
+ });
1572
+ return textResult2({ viewBackgroundColor: color });
1573
+ }
1574
+ );
1135
1575
  server.registerTool(
1136
1576
  "open_canvas",
1137
1577
  {
@@ -1209,6 +1649,57 @@ function registerBaselineTools(server, context) {
1209
1649
  }
1210
1650
  }
1211
1651
  );
1652
+ server.registerTool(
1653
+ "select_objects",
1654
+ {
1655
+ description: "Select existing objects in the connected browser.",
1656
+ inputSchema: {
1657
+ ids: z3.array(z3.string())
1658
+ }
1659
+ },
1660
+ async ({ ids }) => {
1661
+ try {
1662
+ const selectedIds = [];
1663
+ const missingIds = [];
1664
+ for (const id of ids) {
1665
+ if (context.controller.getObject(id)) {
1666
+ selectedIds.push(id);
1667
+ } else {
1668
+ missingIds.push(id);
1669
+ }
1670
+ }
1671
+ const result = await context.requestSetSelection(selectedIds);
1672
+ return textResult2({
1673
+ selectedIds: result.selectedIds,
1674
+ missingIds
1675
+ });
1676
+ } catch (error) {
1677
+ return errorResult2(error);
1678
+ }
1679
+ }
1680
+ );
1681
+ server.registerTool(
1682
+ "undo",
1683
+ {
1684
+ description: "Undo the most recent authoritative scene change.",
1685
+ inputSchema: {}
1686
+ },
1687
+ async () => {
1688
+ const undone = context.controller.undo();
1689
+ return textResult2({ version: context.controller.currentVersion(), undone });
1690
+ }
1691
+ );
1692
+ server.registerTool(
1693
+ "redo",
1694
+ {
1695
+ description: "Redo the most recently undone authoritative scene change.",
1696
+ inputSchema: {}
1697
+ },
1698
+ async () => {
1699
+ const redone = context.controller.redo();
1700
+ return textResult2({ version: context.controller.currentVersion(), redone });
1701
+ }
1702
+ );
1212
1703
  }
1213
1704
  function textResult2(value) {
1214
1705
  return {
@@ -1241,7 +1732,8 @@ function buildMcpServer(options) {
1241
1732
  workspace: options.workspace,
1242
1733
  clientsConnected: options.clientsConnected,
1243
1734
  requestExport: options.requestExport,
1244
- requestSelection: options.requestSelection
1735
+ requestSelection: options.requestSelection,
1736
+ requestSetSelection: options.requestSetSelection
1245
1737
  });
1246
1738
  options.plugin.registerTools(server, {
1247
1739
  controller: options.controller
@@ -1356,6 +1848,10 @@ var CanvasController = class {
1356
1848
  txDepth = 0;
1357
1849
  txDirty = false;
1358
1850
  txOrigin;
1851
+ txSnapshot;
1852
+ undoStack = [];
1853
+ redoStack = [];
1854
+ maxHistory = 50;
1359
1855
  get canvasName() {
1360
1856
  return this.plugin.name;
1361
1857
  }
@@ -1404,12 +1900,17 @@ var CanvasController = class {
1404
1900
  return JSON.stringify(this.plugin.serialize(this.scene), null, 2);
1405
1901
  }
1406
1902
  deserialize(raw) {
1903
+ const before = cloneScene(this.scene);
1407
1904
  const currentVersion = this.scene.version;
1408
1905
  this.scene = this.plugin.deserialize(raw);
1409
1906
  this.scene.version = currentVersion;
1907
+ this.pushUndo(before);
1908
+ this.redoStack.length = 0;
1410
1909
  this.bumpAndNotify();
1411
1910
  }
1412
1911
  replaceFromBrowser(elements, appState, files, origin) {
1912
+ this.pushUndo(cloneScene(this.scene));
1913
+ this.redoStack.length = 0;
1413
1914
  this.scene.elements = elements;
1414
1915
  this.scene.appState = {
1415
1916
  viewBackgroundColor: appState?.viewBackgroundColor ?? this.scene.appState.viewBackgroundColor
@@ -1418,24 +1919,73 @@ var CanvasController = class {
1418
1919
  this.bumpAndNotify(origin);
1419
1920
  }
1420
1921
  mutateScene(mutator, origin) {
1922
+ if (this.txDepth > 0) {
1923
+ const result2 = mutator(this.scene);
1924
+ this.bumpAndNotify(origin);
1925
+ return result2;
1926
+ }
1927
+ const before = cloneScene(this.scene);
1421
1928
  const result = mutator(this.scene);
1929
+ this.pushUndo(before);
1930
+ this.redoStack.length = 0;
1422
1931
  this.bumpAndNotify(origin);
1423
1932
  return result;
1424
1933
  }
1425
1934
  transaction(fn, origin) {
1935
+ const isOuterTransaction = this.txDepth === 0;
1936
+ if (isOuterTransaction) {
1937
+ this.txSnapshot = cloneScene(this.scene);
1938
+ }
1426
1939
  this.txDepth += 1;
1940
+ let failed = false;
1427
1941
  try {
1428
1942
  return fn();
1943
+ } catch (error) {
1944
+ failed = true;
1945
+ if (isOuterTransaction && this.txSnapshot) {
1946
+ this.scene = this.txSnapshot;
1947
+ this.txDirty = false;
1948
+ this.txOrigin = void 0;
1949
+ }
1950
+ throw error;
1429
1951
  } finally {
1430
1952
  this.txDepth -= 1;
1431
- if (this.txDepth === 0 && this.txDirty) {
1953
+ if (this.txDepth === 0 && failed) {
1954
+ this.txSnapshot = void 0;
1955
+ } else if (this.txDepth === 0 && this.txDirty) {
1432
1956
  const txOrigin = this.txOrigin;
1957
+ const snapshot = this.txSnapshot;
1433
1958
  this.txDirty = false;
1434
1959
  this.txOrigin = void 0;
1960
+ this.txSnapshot = void 0;
1961
+ if (snapshot) {
1962
+ this.pushUndo(snapshot);
1963
+ this.redoStack.length = 0;
1964
+ }
1435
1965
  this.commit(txOrigin ?? origin);
1966
+ } else if (this.txDepth === 0) {
1967
+ this.txSnapshot = void 0;
1436
1968
  }
1437
1969
  }
1438
1970
  }
1971
+ undo() {
1972
+ const previous = this.undoStack.pop();
1973
+ if (!previous) {
1974
+ return false;
1975
+ }
1976
+ this.redoStack.push(cloneScene(this.scene));
1977
+ this.restoreScene(previous);
1978
+ return true;
1979
+ }
1980
+ redo() {
1981
+ const next = this.redoStack.pop();
1982
+ if (!next) {
1983
+ return false;
1984
+ }
1985
+ this.undoStack.push(cloneScene(this.scene));
1986
+ this.restoreScene(next);
1987
+ return true;
1988
+ }
1439
1989
  bumpAndNotify(origin) {
1440
1990
  if (this.txDepth > 0) {
1441
1991
  this.txDirty = true;
@@ -1448,6 +1998,18 @@ var CanvasController = class {
1448
1998
  this.scene.version += 1;
1449
1999
  this.listener?.(this.getSnapshot(), origin);
1450
2000
  }
2001
+ pushUndo(scene) {
2002
+ this.undoStack.push(cloneScene(scene));
2003
+ if (this.undoStack.length > this.maxHistory) {
2004
+ this.undoStack.shift();
2005
+ }
2006
+ }
2007
+ restoreScene(scene) {
2008
+ const currentVersion = this.scene.version;
2009
+ this.scene = cloneScene(scene);
2010
+ this.scene.version = currentVersion;
2011
+ this.commit();
2012
+ }
1451
2013
  };
1452
2014
 
1453
2015
  // src/server/workspace.ts
@@ -1520,6 +2082,7 @@ var WsBridge = class {
1520
2082
  clients = /* @__PURE__ */ new Map();
1521
2083
  pendingExports = /* @__PURE__ */ new Map();
1522
2084
  pendingSelections = /* @__PURE__ */ new Map();
2085
+ pendingSelectionSets = /* @__PURE__ */ new Map();
1523
2086
  syncOrder = 0;
1524
2087
  wss;
1525
2088
  attach(server, path5 = "/ws") {
@@ -1588,6 +2151,31 @@ var WsBridge = class {
1588
2151
  client.socket.send(JSON.stringify(request));
1589
2152
  });
1590
2153
  }
2154
+ requestSetSelection(selectedIds, options = {}) {
2155
+ const client = this.mostRecentlySyncedClient((candidate) => candidate.supportsSelectionSet);
2156
+ if (!client) {
2157
+ if (this.connectedClientCount() > 0) {
2158
+ return Promise.reject(
2159
+ new Error("No browser canvas client supports programmatic selection")
2160
+ );
2161
+ }
2162
+ return Promise.reject(new Error("No browser canvas client is connected"));
2163
+ }
2164
+ const id = randomUUID4();
2165
+ const request = {
2166
+ type: "selection:set",
2167
+ id,
2168
+ selectedIds
2169
+ };
2170
+ return new Promise((resolve, reject) => {
2171
+ const timer = setTimeout(() => {
2172
+ this.pendingSelectionSets.delete(id);
2173
+ reject(new Error("Selection set timed out"));
2174
+ }, options.timeoutMs ?? 5e3);
2175
+ this.pendingSelectionSets.set(id, { resolve, reject, timer });
2176
+ client.socket.send(JSON.stringify(request));
2177
+ });
2178
+ }
1591
2179
  close() {
1592
2180
  for (const { socket } of this.clients.values()) {
1593
2181
  socket.close();
@@ -1598,7 +2186,8 @@ var WsBridge = class {
1598
2186
  this.clients.set(socket, {
1599
2187
  socket,
1600
2188
  lastSyncedOrder: 0,
1601
- lastSyncedVersion: -1
2189
+ lastSyncedVersion: -1,
2190
+ supportsSelectionSet: false
1602
2191
  });
1603
2192
  socket.on("message", (data) => this.handleMessage(socket, data.toString()));
1604
2193
  socket.on("close", () => {
@@ -1612,6 +2201,10 @@ var WsBridge = class {
1612
2201
  return;
1613
2202
  }
1614
2203
  if (message.type === "hello") {
2204
+ const client = this.clients.get(socket);
2205
+ if (client) {
2206
+ client.supportsSelectionSet = Boolean(message.capabilities?.selectionSet);
2207
+ }
1615
2208
  this.sendScene(socket, this.controller.getSnapshot());
1616
2209
  return;
1617
2210
  }
@@ -1655,6 +2248,20 @@ var WsBridge = class {
1655
2248
  } else {
1656
2249
  pending.reject(new Error(message.message));
1657
2250
  }
2251
+ return;
2252
+ }
2253
+ if (message.type === "selection:set:result" || message.type === "selection:set:error") {
2254
+ const pending = this.pendingSelectionSets.get(message.id);
2255
+ if (!pending) {
2256
+ return;
2257
+ }
2258
+ clearTimeout(pending.timer);
2259
+ this.pendingSelectionSets.delete(message.id);
2260
+ if (message.type === "selection:set:result") {
2261
+ pending.resolve({ selectedIds: message.selectedIds });
2262
+ } else {
2263
+ pending.reject(new Error(message.message));
2264
+ }
1658
2265
  }
1659
2266
  }
1660
2267
  sendScene(socket, snapshot) {
@@ -1680,8 +2287,8 @@ var WsBridge = class {
1680
2287
  client.lastSyncedOrder = ++this.syncOrder;
1681
2288
  client.lastSyncedVersion = version;
1682
2289
  }
1683
- mostRecentlySyncedClient() {
1684
- return [...this.clients.values()].filter((candidate) => candidate.socket.readyState === WebSocket.OPEN).sort((left, right) => right.lastSyncedOrder - left.lastSyncedOrder)[0];
2290
+ mostRecentlySyncedClient(predicate = () => true) {
2291
+ return [...this.clients.values()].filter((candidate) => candidate.socket.readyState === WebSocket.OPEN && predicate(candidate)).sort((left, right) => right.lastSyncedOrder - left.lastSyncedOrder)[0];
1685
2292
  }
1686
2293
  };
1687
2294
 
@@ -1702,7 +2309,8 @@ async function startHttpServer(options) {
1702
2309
  allowedHosts: allowedHostsFor(options.host, port),
1703
2310
  clientsConnected: () => bridge.connectedClientCount(),
1704
2311
  requestExport: (exportOptions) => bridge.requestExport(exportOptions),
1705
- requestSelection: (selectionOptions) => bridge.requestSelection(selectionOptions)
2312
+ requestSelection: (selectionOptions) => bridge.requestSelection(selectionOptions),
2313
+ requestSetSelection: (selectedIds, selectionOptions) => bridge.requestSetSelection(selectedIds, selectionOptions)
1706
2314
  });
1707
2315
  const server = createServer(app);
1708
2316
  bridge.attach(server);