mulmoterminal 4.16.1 → 4.18.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 (155) hide show
  1. package/README.md +7 -5
  2. package/common/dirPathKey.ts +19 -0
  3. package/common/shortcuts.ts +13 -0
  4. package/common/toolGroups.ts +39 -3
  5. package/common/toolbarPins.ts +107 -0
  6. package/dist/assets/{AllPackages-CF2GkZ5g-BR1ZQVLR.js → AllPackages-CF2GkZ5g-BpghxJjj.js} +1 -1
  7. package/dist/assets/{BaseConfiguration-CAwZOAki-CH1MWBaY.js → BaseConfiguration-CAwZOAki-WQURtaX1.js} +1 -1
  8. package/dist/assets/{Element-BMI7Nj5w-U7cD6zKw.js → Element-BMI7Nj5w-CcDgA_9-.js} +1 -1
  9. package/dist/assets/{Entities-C8p_0jCn-dSDbGTsJ.js → Entities-C8p_0jCn-DRWzxsVw.js} +1 -1
  10. package/dist/assets/{FunctionList-YWk54TCy-BkQb8DA8.js → FunctionList-YWk54TCy-BrIR4n-1.js} +1 -1
  11. package/dist/assets/{InputJax-C5dj-ntL-BN7NCKKO.js → InputJax-C5dj-ntL-AT5iU-td.js} +1 -1
  12. package/dist/assets/{MathItem-YJvQL-XJ-DIEn5osO.js → MathItem-YJvQL-XJ-CxJPVleP.js} +1 -1
  13. package/dist/assets/{MmlFactory-DuXYIgx8-jZrlbK77.js → MmlFactory-DuXYIgx8-Ey_YXuDi.js} +1 -1
  14. package/dist/assets/{Options-oARQeeuT-BFoe45j7.js → Options-oARQeeuT-BZ9bNEz5.js} +1 -1
  15. package/dist/assets/{OutputJax-DfUZNNCu-D647fvVr.js → OutputJax-DfUZNNCu-De4Z1Pn_.js} +1 -1
  16. package/dist/assets/{PrioritizedList-6lun1Xqd-BDXLCciz.js → PrioritizedList-6lun1Xqd-BTQmhniB.js} +1 -1
  17. package/dist/assets/{Styles-BPXeoqzi-CWQXCVHq.js → Styles-BPXeoqzi-Cwo9PIXj.js} +1 -1
  18. package/dist/assets/{TeXAtom-LHT3KFWH-BXe_YUAD.js → TeXAtom-LHT3KFWH-B58fWn67.js} +1 -1
  19. package/dist/assets/{abnfDiagram-VCTEODGH-DoYNjdBB-BKBHvI11.js → abnfDiagram-VCTEODGH-DoYNjdBB-N4p9eIcR.js} +1 -1
  20. package/dist/assets/{arc-CqByPKkF-B2A6kmY0.js → arc-CqByPKkF-o90vYcFT.js} +1 -1
  21. package/dist/assets/architecture-7GRP2DOG-CRS69XmP-Dofb7uTe.js +1 -0
  22. package/dist/assets/{architectureDiagram-5GKGNRK7-BhcT6qS5-CZaKxcvH.js → architectureDiagram-5GKGNRK7-BhcT6qS5-y2WkXlFP.js} +1 -1
  23. package/dist/assets/{assistive-mml-DXJXho4o-Duj6EibK.js → assistive-mml-DXJXho4o-DHpoehaR.js} +1 -1
  24. package/dist/assets/{blockDiagram-I7D4REHJ-1Cng0Q4K-BXZWrL4K.js → blockDiagram-I7D4REHJ-1Cng0Q4K-BDINYHUk.js} +1 -1
  25. package/dist/assets/{c4Diagram-7LVT6UL2-CAb9c8nc-XML4meyN.js → c4Diagram-7LVT6UL2-CAb9c8nc-BtCR-OQb.js} +1 -1
  26. package/dist/assets/channel-BjESmYnj-D2HJk1kK.js +1 -0
  27. package/dist/assets/{chunk-4HAMMTFA-ChwfIAnF-D3Xu-FmM.js → chunk-4HAMMTFA-ChwfIAnF-BoqlitDw.js} +1 -1
  28. package/dist/assets/{chunk-75Z2AOVW-CbPA0Zt_-B3EpUXO6.js → chunk-75Z2AOVW-CbPA0Zt_-sG1wziTf.js} +1 -1
  29. package/dist/assets/{chunk-DU6HZSFF-DuOx2LFX-BNLbgzMj.js → chunk-DU6HZSFF-DuOx2LFX-jw2gw2WG.js} +1 -1
  30. package/dist/assets/{chunk-F27PBJKO-CgBMw9yN-DGYrs_Xh.js → chunk-F27PBJKO-CgBMw9yN-BIA-Rq1B.js} +1 -1
  31. package/dist/assets/{chunk-GMAD6QVW-JTqlimUX-D6fbHeH8.js → chunk-GMAD6QVW-JTqlimUX-B7VyMMki.js} +1 -1
  32. package/dist/assets/{chunk-GVQU2GXP-D6ccB4DO-CLm1L51U.js → chunk-GVQU2GXP-D6ccB4DO-CxN0Y161.js} +1 -1
  33. package/dist/assets/{chunk-IMKFNOWR-ij1DSzMb-3ueGg7Ju.js → chunk-IMKFNOWR-ij1DSzMb-Cp5MHDVx.js} +1 -1
  34. package/dist/assets/{chunk-L3NEJ4N5-CUoAQVG2-C2QaFk-g.js → chunk-L3NEJ4N5-CUoAQVG2-Cq8hXkjf.js} +1 -1
  35. package/dist/assets/{chunk-OSK3NFVY-_VNbXQSu-C7F93M3m.js → chunk-OSK3NFVY-_VNbXQSu-CLHHccp_.js} +1 -1
  36. package/dist/assets/{chunk-P2QGCYS3-B8lzOn3Y-DqTS4kWt.js → chunk-P2QGCYS3-B8lzOn3Y-9R8JiTOW.js} +1 -1
  37. package/dist/assets/{chunk-POPQ4Y6H-Bh_uOTmp-yzH0pUKG.js → chunk-POPQ4Y6H-Bh_uOTmp-IIk4SISs.js} +1 -1
  38. package/dist/assets/{chunk-PWAF6VOD-DzjnfYSw-Bkp-2n9L.js → chunk-PWAF6VOD-DzjnfYSw-FXKbHA4z.js} +1 -1
  39. package/dist/assets/{chunk-SHT3W25Y-B8dQLS5w-B6vDUDbt.js → chunk-SHT3W25Y-B8dQLS5w-asO6U825.js} +1 -1
  40. package/dist/assets/{chunk-SVP7TREG-B6A-T8Th-C36d9rwX.js → chunk-SVP7TREG-B6A-T8Th-C_31sNJS.js} +1 -1
  41. package/dist/assets/{chunk-TICWLB2K-C1kwD29l-8bma78sl.js → chunk-TICWLB2K-C1kwD29l-De2Aa9yO.js} +1 -1
  42. package/dist/assets/{chunk-XXDRQBXY-vg0kfC73-DikArmyN.js → chunk-XXDRQBXY-vg0kfC73-MJrwfLy3.js} +1 -1
  43. package/dist/assets/classDiagram-ZZMXUADV-C7gP-pUH-CTNdATck.js +1 -0
  44. package/dist/assets/classDiagram-v2-VYDZK3BY-CUwV5k1F-CTNdATck.js +1 -0
  45. package/dist/assets/{cose-bilkent-JH36ORCC-NuHCtE3P-B1XlcY4_.js → cose-bilkent-JH36ORCC-NuHCtE3P-CBmhuRye.js} +1 -1
  46. package/dist/assets/cynefin-OW5HDTMX-C4iKivN4-BAbBOyvn.js +1 -0
  47. package/dist/assets/{cynefinDiagram-5FMLGOSQ-CA7sro34-Dpxb2Y3q.js → cynefinDiagram-5FMLGOSQ-CA7sro34-C7EC0rCC.js} +1 -1
  48. package/dist/assets/{dagre-GXQ25YYZ-DOztjUR_-tCVxsIWx.js → dagre-GXQ25YYZ-DOztjUR_-DUYDKcad.js} +1 -1
  49. package/dist/assets/{diagram-S7CK7UJ4-D_nsLfRT-C0oowP2x.js → diagram-S7CK7UJ4-D_nsLfRT-6xfPNGOi.js} +1 -1
  50. package/dist/assets/{diagram-UQ7AKVKN-DNN-YX9a-BhdVmuTQ.js → diagram-UQ7AKVKN-DNN-YX9a-DabWPcsA.js} +1 -1
  51. package/dist/assets/{diagram-VSXAHHWV--HENzo-l-CpmR-QX7.js → diagram-VSXAHHWV--HENzo-l-0O7qAbYx.js} +1 -1
  52. package/dist/assets/{diagram-VX7I27RA-m2m9dROj-BHMZbFwF.js → diagram-VX7I27RA-m2m9dROj-DSsL6lbk.js} +1 -1
  53. package/dist/assets/{diagram-Z3DM3KII-D4qpv4-B-BdXuf-Jf.js → diagram-Z3DM3KII-D4qpv4-B-x5aaLfdn.js} +1 -1
  54. package/dist/assets/{dist-G6SuIVg-.js → dist-BHbP5Rdy.js} +1 -1
  55. package/dist/assets/{dist-C9lyUYv8.js → dist-CI0Sz8VW.js} +1 -1
  56. package/dist/assets/{dist-DeiyD9Ki-DblgSNGE.js → dist-DeiyD9Ki-CyYR8D-0.js} +1 -1
  57. package/dist/assets/{dist-DKTnxp-h.js → dist-KHYYM69R.js} +1 -1
  58. package/dist/assets/{dist-DAo7Oyn5.js → dist-to-wCxn9.js} +1 -1
  59. package/dist/assets/{ebnfDiagram-PWID7BFC-CAbv3sZg-C48moA_l.js → ebnfDiagram-PWID7BFC-CAbv3sZg-CzJDavSQ.js} +1 -1
  60. package/dist/assets/{erDiagram-RLTQ6QDP-a10Ryh0--Mt1Za2Av.js → erDiagram-RLTQ6QDP-a10Ryh0--BfSvF1ta.js} +1 -1
  61. package/dist/assets/eventmodeling-NTZA5JFV-DhqtNS8R-DZDQj3ay.js +1 -0
  62. package/dist/assets/flowDiagram-HODETNUW-CAW_8mwY-B4f7Dpo6.js +1 -0
  63. package/dist/assets/{ganttDiagram-EL5Y4UJY-Cb7leeOC-7Ln8AUnj.js → ganttDiagram-EL5Y4UJY-Cb7leeOC-DKtB1NzV.js} +1 -1
  64. package/dist/assets/gitGraph-4MIJSDKK-S_Zs6Nxs-CZ6NmIpk.js +1 -0
  65. package/dist/assets/{gitGraphDiagram-WWUBYQGX-BCSbUpXG-CqXWIVg7.js → gitGraphDiagram-WWUBYQGX-BCSbUpXG-DKX1wbps.js} +1 -1
  66. package/dist/assets/{html-DdQiKDqM-BHusMiN-.js → html-DdQiKDqM-O0S29Dwr.js} +1 -1
  67. package/dist/assets/index-yoMfkmrj.js +5418 -0
  68. package/dist/assets/info-A6RAGUB7-4sGJh1is-DIi5EBfj.js +1 -0
  69. package/dist/assets/{infoDiagram-27XIBGKW-BJRaZItN-CpPDV4hG.js → infoDiagram-27XIBGKW-BJRaZItN-q1dLBYbI.js} +1 -1
  70. package/dist/assets/{ishikawaDiagram-5VMMS53U-DHlFe__S-Dq_O4Zho.js → ishikawaDiagram-5VMMS53U-DHlFe__S-DEAMGQtX.js} +1 -1
  71. package/dist/assets/{journeyDiagram-3NMN7TZE-BOIo3JM--wMJ4fbc1.js → journeyDiagram-3NMN7TZE-BOIo3JM--BO_7tBTd.js} +1 -1
  72. package/dist/assets/{kanban-definition-UXKFOSKX-yrQdG683-_5KdtmkY.js → kanban-definition-UXKFOSKX-yrQdG683-Bx4it8pk.js} +1 -1
  73. package/dist/assets/{lengths-CvmPfBid-CVCP8twv.js → lengths-CvmPfBid-BJ7XjTXv.js} +1 -1
  74. package/dist/assets/{line-DUb1TjJS-CF13RdVB.js → line-DUb1TjJS-DTQdqqNY.js} +1 -1
  75. package/dist/assets/{linear-xsuGWu5q-CL39nfZD.js → linear-xsuGWu5q-DAJY5eE1.js} +1 -1
  76. package/dist/assets/{liteAdaptor-C0FQiOEx-B7vjjyDw.js → liteAdaptor-C0FQiOEx-CpU2hMwG.js} +1 -1
  77. package/dist/assets/{mathjax-DVEo4UYj-DVc67oTA.js → mathjax-DVEo4UYj-DblCQqUM.js} +1 -1
  78. package/dist/assets/{mermaid-parser.core-fkjeiYO7-ZhNOsYx6.js → mermaid-parser.core-fkjeiYO7-xLW4or9n.js} +2 -2
  79. package/dist/assets/{mermaid.core-CZDx2Cyn-D0adQ2ma.js → mermaid.core-CZDx2Cyn-BWO3dTDf.js} +4 -4
  80. package/dist/assets/{mindmap-definition-YA3MSWOX-BpKAVyqs-DiEKJqkL.js → mindmap-definition-YA3MSWOX-BpKAVyqs-BrJpjBKU.js} +1 -1
  81. package/dist/assets/{mo-CrlNnGXX-CiO8TUoC.js → mo-CrlNnGXX-Eo4cINyg.js} +1 -1
  82. package/dist/assets/packet-AYTQ26CC-MQQyx8fI-yAikr6bQ.js +1 -0
  83. package/dist/assets/{pegDiagram-XKGWAZYB-C7eG_Ohl-C4Enb8H1.js → pegDiagram-XKGWAZYB-C7eG_Ohl-CaXo4ojb.js} +1 -1
  84. package/dist/assets/pie-WAS4IAKB-CyaL5i_d-BIdTCQS6.js +1 -0
  85. package/dist/assets/{pieDiagram-E7YTZNPT-5f9wSfDR-AKlM6fne.js → pieDiagram-E7YTZNPT-5f9wSfDR-BwnQjyyW.js} +1 -1
  86. package/dist/assets/{quadrantDiagram-AXDQQJYC-mWeaOj5S-CAwPohmQ.js → quadrantDiagram-AXDQQJYC-mWeaOj5S-DvsMFOc9.js} +1 -1
  87. package/dist/assets/radar-RG4KPBEZ-DR-SsHDJ-0JhYwnII.js +1 -0
  88. package/dist/assets/railroad-74A4TZTK-C2gqsOGX-C1zJ70Ow.js +1 -0
  89. package/dist/assets/railroad-abnf-HS5TGJTU-Dpzw6P5u-BMKd1LY4.js +1 -0
  90. package/dist/assets/railroad-ebnf-LZEXJU2U-J_BuIrPo-D5xXkaGL.js +1 -0
  91. package/dist/assets/railroad-peg-WCYAUIDC-DzMStOR--CTf0TsuO.js +1 -0
  92. package/dist/assets/{railroadDiagram-O6MQD6OU-DJskhE7u-D88MJVV3.js → railroadDiagram-O6MQD6OU-DJskhE7u-C_0njIFi.js} +1 -1
  93. package/dist/assets/{requirementDiagram-BXWQKSXE-B5E0bU4h-DhMi5aIj.js → requirementDiagram-BXWQKSXE-B5E0bU4h-C5hnNnLY.js} +1 -1
  94. package/dist/assets/{sankeyDiagram-P5KCCOFB-DS3AOTai-BqvLM5FN.js → sankeyDiagram-P5KCCOFB-DS3AOTai-pWFxLlmZ.js} +1 -1
  95. package/dist/assets/{sequenceDiagram-WJ2MYXX4-Bn2L9osv-DD00vS5e.js → sequenceDiagram-WJ2MYXX4-Bn2L9osv-Qj-GBMA5.js} +1 -1
  96. package/dist/assets/{src-CLC0ddSd-BiOrUmL4.js → src-CLC0ddSd-C6SesIb_.js} +1 -1
  97. package/dist/assets/{stateDiagram-D77RDMKH-DbtoW7HL-CIt9LbTt.js → stateDiagram-D77RDMKH-DbtoW7HL-0tDoAFAn.js} +1 -1
  98. package/dist/assets/stateDiagram-v2-MP3YSRHH-CX6RalKY-CyxuK80e.js +1 -0
  99. package/dist/assets/{svg-CaHU_sxA-CHHrP--F.js → svg-CaHU_sxA-hUQUwI5e.js} +1 -1
  100. package/dist/assets/{swimlanes-42K2YHIH-90h8w_3B-DNrY0PsR.js → swimlanes-42K2YHIH-90h8w_3B-BIANSbUk.js} +1 -1
  101. package/dist/assets/swimlanesDiagram-VR7AAH4N-49XH_TMr-nlZP2lTW.js +8 -0
  102. package/dist/assets/{tex-B1cONMvb-BZ8-XPoY.js → tex-B1cONMvb-BuuMe8DF.js} +1 -1
  103. package/dist/assets/{timeline-definition-24CTP7MA-Bx3KcICO-C2qvbp_0.js → timeline-definition-24CTP7MA-Bx3KcICO-uJwxqLxL.js} +1 -1
  104. package/dist/assets/treeView-Q6P3EWNA-gFs854Sn-BDnUAqSd.js +1 -0
  105. package/dist/assets/treemap-WGGIJYW6-DGh_szUA-BcyHS0XZ.js +1 -0
  106. package/dist/assets/{vennDiagram-4TSXK5OY-DSexNe_H-BYZN3BN_.js → vennDiagram-4TSXK5OY-DSexNe_H-CEWVEpb7.js} +1 -1
  107. package/dist/assets/wardley-WFR3VGLG-BVLYL3gB-CTDrPm0c.js +1 -0
  108. package/dist/assets/{wardleyDiagram-VM6X3IG4-DesYYyZW-Du8b9LqG.js → wardleyDiagram-VM6X3IG4-DesYYyZW-7T-CCnI4.js} +1 -1
  109. package/dist/assets/{xychartDiagram-S5SC5T6Z-dIt6pHWV-R_c8Thbw.js → xychartDiagram-S5SC5T6Z-dIt6pHWV-BWhy5WEU.js} +1 -1
  110. package/dist/index.html +1 -1
  111. package/package.json +3 -2
  112. package/plugins/plugins.json +2 -1
  113. package/server/agents/codex-sessions.ts +7 -7
  114. package/server/agents/codex-thread-names.ts +87 -0
  115. package/server/agents/codex-user-turn.ts +86 -0
  116. package/server/backends/fileChange.ts +9 -0
  117. package/server/backends/mulmoscript.ts +14 -8
  118. package/server/backends/openPath.ts +6 -0
  119. package/server/backends/presentPathRoot.ts +7 -1
  120. package/server/backends/shapescript.ts +51 -0
  121. package/server/backends/shortcuts.ts +47 -16
  122. package/server/config/app-config.ts +10 -0
  123. package/server/config/config-body.ts +1 -0
  124. package/server/infra/host-tools.ts +13 -1
  125. package/server/infra/plugins-registry.ts +27 -10
  126. package/server/infra/shapescript-render-tool.ts +79 -0
  127. package/server/routes/app-routes.ts +7 -0
  128. package/server/routes/plugin-routes.ts +21 -0
  129. package/server/routes/ws-routes.ts +5 -1
  130. package/server/session/last-turn.ts +9 -9
  131. package/server/session/prompt-history.ts +25 -8
  132. package/server/session/session-resolve.ts +15 -1
  133. package/server/skills/mulmoterminal-config/SKILL.md +29 -1
  134. package/dist/assets/architecture-7GRP2DOG-CRS69XmP-DmZ-HChU.js +0 -1
  135. package/dist/assets/channel-BjESmYnj-BFW3bEkR.js +0 -1
  136. package/dist/assets/classDiagram-ZZMXUADV-C7gP-pUH-BUgE4GLJ.js +0 -1
  137. package/dist/assets/classDiagram-v2-VYDZK3BY-CUwV5k1F-BUgE4GLJ.js +0 -1
  138. package/dist/assets/cynefin-OW5HDTMX-C4iKivN4-bsjBxOW3.js +0 -1
  139. package/dist/assets/eventmodeling-NTZA5JFV-DhqtNS8R-DEQxhhe6.js +0 -1
  140. package/dist/assets/flowDiagram-HODETNUW-CAW_8mwY-CeF3rDxG.js +0 -1
  141. package/dist/assets/gitGraph-4MIJSDKK-S_Zs6Nxs-CB3gPIVK.js +0 -1
  142. package/dist/assets/index-CdmvtxCQ.js +0 -1031
  143. package/dist/assets/info-A6RAGUB7-4sGJh1is-4bRJ5Ro5.js +0 -1
  144. package/dist/assets/packet-AYTQ26CC-MQQyx8fI-MEZA5KJl.js +0 -1
  145. package/dist/assets/pie-WAS4IAKB-CyaL5i_d-BIbJfKkP.js +0 -1
  146. package/dist/assets/radar-RG4KPBEZ-DR-SsHDJ-HLbo3sJW.js +0 -1
  147. package/dist/assets/railroad-74A4TZTK-C2gqsOGX-DuC_Eg0T.js +0 -1
  148. package/dist/assets/railroad-abnf-HS5TGJTU-Dpzw6P5u-CCehLGc2.js +0 -1
  149. package/dist/assets/railroad-ebnf-LZEXJU2U-J_BuIrPo-CpfaYN3N.js +0 -1
  150. package/dist/assets/railroad-peg-WCYAUIDC-DzMStOR--CAPvB8Q4.js +0 -1
  151. package/dist/assets/stateDiagram-v2-MP3YSRHH-CX6RalKY-B9ca3LBb.js +0 -1
  152. package/dist/assets/swimlanesDiagram-VR7AAH4N-49XH_TMr-CwENk-w_.js +0 -8
  153. package/dist/assets/treeView-Q6P3EWNA-gFs854Sn-Bq0gV9iU.js +0 -1
  154. package/dist/assets/treemap-WGGIJYW6-DGh_szUA-D1QZ74up.js +0 -1
  155. package/dist/assets/wardley-WFR3VGLG-BVLYL3gB-D_rI3R6m.js +0 -1
package/README.md CHANGED
@@ -1083,11 +1083,13 @@ everything else on disk that happens to parse as a deck. Measured on a real work
1083
1083
  matched, 33 were the user's own decks and 217 were a checked-out repository's test fixtures and
1084
1084
  samples.** A menu is a short list of things you chose.
1085
1085
 
1086
- **Where the decks have to live:** in any directory MulmoTerminal knows about — the workspace it
1087
- was started in, plus the directories in your launcher's saved list (up to 64 in total, and one that
1088
- is no longer on disk is skipped). Those are read **once at startup**, so a repository you open for
1089
- the first time needs a restart before its decks can be shown. Any deck you can see in the file tree is also reachable there — right-click a row and choose
1090
- **Open in the Canvas** — with no configuration at all.
1086
+ **Where the decks have to live:** anywhere. A deck under the workspace — or under a directory in
1087
+ your launcher's saved list (up to 64 in total, read once at startup) — is addressed by its place in
1088
+ that directory; one anywhere else is addressed by its own path. Either way it opens, and a deck you
1089
+ open both ways is **one card**, not two. A deck you can see in the file tree is reachable there too
1090
+ — right-click a row and choose **Open in the Canvas** — with no configuration at all, as long as the
1091
+ pane knows the directory its tree is rooted at. It shows that path in its header; a pane still
1092
+ reading `(default workspace)` has nothing to resolve a row against and offers no entry.
1091
1093
 
1092
1094
  If a deck cannot be opened (it was deleted, or the workspace moved since startup), the cell says
1093
1095
  why rather than doing nothing.
@@ -43,3 +43,22 @@ export function dirPathKey(path: string): string {
43
43
 
44
44
  /** Whether two paths name the same directory, as far as spelling can tell. */
45
45
  export const isSameDirPath = (a: string | null | undefined, b: string | null | undefined): boolean => !!a && !!b && dirPathKey(a) === dirPathKey(b);
46
+
47
+ /**
48
+ * Whether `path` names one file on its own — POSIX `/…`, a Windows drive root, a UNC share.
49
+ *
50
+ * NARROWER than `rootOf` on purpose, in the one place the two could disagree: a SINGLE leading
51
+ * backslash. `rootOf` keys `\deck.json` as `/deck.json`, which is right for its job (comparing two
52
+ * spellings of a directory lexically) and wrong for this one — on Windows that path is rooted on
53
+ * the process's CURRENT DRIVE, which the spelling does not carry and a browser cannot infer. Keying
54
+ * it would give `\deck.json` and `C:\deck.json` two identities for one file while conflating the
55
+ * first with a POSIX `/deck.json` (Codex P2 on #1976). A caller that needs a decision here gets
56
+ * "no", and whatever it falls back to is at worst what it did before.
57
+ *
58
+ * The same distinction `rootOf` already draws for `C:foo` and `\server\share`: a spelling that
59
+ * means something else must not borrow a rooted one's key.
60
+ */
61
+ export const isRootedPath = (path: string): boolean => {
62
+ const trimmed = path.trim();
63
+ return /^[a-zA-Z]:[/\\]/.test(trimmed) || /^[/\\]{2}/.test(trimmed) || trimmed.startsWith("/");
64
+ };
@@ -6,6 +6,11 @@
6
6
  // show up in the other — so the on-disk format is the contract: an OBJECT WRAPPER
7
7
  // `{ shortcuts: Shortcut[] }`, not a bare array, matching mulmoclaude/src/types/shortcuts.ts.
8
8
  // Keep this type in sync with MulmoClaude's.
9
+ //
10
+ // It is a SUBSET of what a record may hold, not the whole of it: the file is the union of every
11
+ // version of both apps that has written it, and a stored record can carry fields this build has
12
+ // never heard of. `toShortcut` (server/backends/shortcuts.ts) carries those through rather than
13
+ // dropping them (#1996) — so this type says what MulmoTerminal understands, not what exists.
9
14
 
10
15
  export const SHORTCUT_KINDS = ["collection", "feed"] as const;
11
16
  export type ShortcutKind = (typeof SHORTCUT_KINDS)[number];
@@ -19,6 +24,14 @@ export interface Shortcut {
19
24
  title: string;
20
25
  /** Cached material-symbols glyph — refreshed on reconcile. */
21
26
  icon: string;
27
+ /** Cached accent colour name — MulmoClaude's, and only MulmoClaude draws it (#1993).
28
+ *
29
+ * Carried rather than understood: this app has no accent palette, so it keeps whatever string
30
+ * the file holds and hands it back unchanged. Deliberately NOT validated here — deciding which
31
+ * colours are legal is the job of the app that draws them, and a check that drifted from theirs
32
+ * would silently delete a colour they consider valid, which is the bug this field exists to fix.
33
+ * Absent when the collection names none. */
34
+ color?: string;
22
35
  }
23
36
 
24
37
  /** True when two shortcuts target the same thing (the dedupe key). */
@@ -8,7 +8,12 @@
8
8
  // config already written against it.
9
9
 
10
10
  // The groups, ordered by how much damage a call can do.
11
- // render — draws into the Canvas panel and stops there. No side effect outside it.
11
+ // render — draws a model or document for the user, and writes nothing but the
12
+ // artifact it just drew. It USED to mean "no side effect outside the
13
+ // Canvas panel"; presentShapeScript saves its `.shape` and
14
+ // renderShapeScript saves a PNG, so the honest line is that a render
15
+ // tool touches only its own output, never the workspace's data or
16
+ // anything off this machine.
12
17
  // data — reads/writes the workspace's structured data (collections, accounting).
13
18
  // media — generation that is slow, costly, and lands files on disk.
14
19
  // external — reaches a third-party account or API.
@@ -84,6 +89,11 @@ const GROUP_BY_TOOL = new Map<string, ToolGroup>([
84
89
  ["presentForm", "render"],
85
90
  ["presentChart", "render"],
86
91
  ["presentHtml", "render"],
92
+ ["presentShapeScript", "render"],
93
+ // Beside presentShapeScript deliberately: a cell that can show a 3D model should be
94
+ // able to CHECK one, and splitting the pair would leave an agent able to present a
95
+ // model it cannot look at first.
96
+ ["renderShapeScript", "render"],
87
97
 
88
98
  // presentCollection RENDERS, but it renders collection data and only makes sense next to
89
99
  // manageCollection — a cell offered the view without the store gets a tool it cannot fill.
@@ -175,8 +185,34 @@ export const LEGACY_GUI_SERVER_IDS: readonly string[] = ["mulmoterminal-gui"];
175
185
  // model spend money silently under a switch the UI presents as "let the agent draw", so it
176
186
  // keeps Claude Code's prompt (answer it once per project and the prompt stops).
177
187
  //
178
- // The three below save an artifact and draw it, and call nothing external.
179
- export const AUTO_ALLOWED_TOOLS: readonly string[] = ["presentForm", "presentChart", "presentHtml"];
188
+ // The four below save an artifact and draw it, and call nothing external.
189
+ //
190
+ // `presentShapeScript` was listed here on the grounds that it saved nothing at all —
191
+ // the source travelled in the tool result and was rendered client-side. That stopped
192
+ // being true at shapescript-plugin 1.1.0, which writes the model to
193
+ // `artifacts/shapes/` and can open a `.shape` the caller names. It stays on the list,
194
+ // but now for the same reason as the other three rather than a stronger one that no
195
+ // longer holds.
196
+ //
197
+ // `renderShapeScript` is here too, and the reasoning is worth stating because the first
198
+ // attempt got it wrong. It starts a PROCESS — a headless browser — which reads like the
199
+ // far side of "calls nothing external", so it was left off. But this bar is about REACH
200
+ // and COST, not about process creation: the tools kept off it spend money
201
+ // (presentDocument's image fill), publish to the internet, or act in someone else's
202
+ // account. A local Chromium rendering a page WE construct, with every request it makes
203
+ // aborted unless it is one of our two assets, does none of that. It writes a PNG under
204
+ // the workspace artifacts and burns CPU for at most the render budget.
205
+ //
206
+ // Leaving it off was also not the half-measure it looked like. This list governs GRID
207
+ // cells; the workspace passes `allowedToolNames()`, which auto-approves everything not
208
+ // in NEVER_AUTO_APPROVED_TOOLS — so the tool prompted in a cell and ran unattended in
209
+ // the workspace, which is the worst of both and matches no stated policy (codex on
210
+ // #2010). The two paths now agree.
211
+ //
212
+ // And the friction had no payoff: the tool exists so an agent can CHECK a model before
213
+ // showing it, which is a render-look-fix loop. A prompt in the middle of that costs the
214
+ // user attention to approve the agent looking at its own work.
215
+ export const AUTO_ALLOWED_TOOLS: readonly string[] = ["presentForm", "presentChart", "presentHtml", "presentShapeScript", "renderShapeScript"];
180
216
 
181
217
  /** Tools that must keep the agent's permission prompt on EVERY claude session, including the
182
218
  * workspace.
@@ -0,0 +1,107 @@
1
+ // Which of the pinned favourites the app toolbar shows without opening Collections (#1984).
2
+ //
3
+ // Shared because both sides decide from it: the server sanitizes what `~/.mulmoterminal/config.json`
4
+ // says, the browser renders the toolbar and writes the same field back.
5
+ //
6
+ // Deliberately NOT a field on `Shortcut`. The pins themselves live in `<workspace>/config/shortcuts.json`,
7
+ // which MulmoTerminal SHARES with MulmoClaude, and both apps rebuild every record when they write it
8
+ // (`normalizeShortcuts`) — so a field one app added is dropped the first time the other pins anything.
9
+ // This is a MulmoTerminal-side view preference over that shared list, and it holds only the identity
10
+ // of the pins it promotes: the label and the icon are read from the pin, so renaming a collection
11
+ // cannot leave the toolbar saying the old name.
12
+ import { SHORTCUT_KINDS, type Shortcut, type ShortcutKind } from "./shortcuts.js";
13
+
14
+ /** How many buttons the toolbar draws, and so how many pins can be promoted at once. The row
15
+ * already carries the view switch, the grid's own controls, the status tally and two gauges; past
16
+ * a handful the pins stop being "one press away" and start pushing that row into its horizontal
17
+ * scroll, which is the two-step this feature exists to remove. */
18
+ export const MAX_TOOLBAR_PINS = 5;
19
+
20
+ /** How many keys the FILE may hold, which is not the same number.
21
+ *
22
+ * Nothing here ever deletes a key it did not just demote: unpinning a promoted collection leaves
23
+ * its key in the config, where it draws nothing and takes no slot — and re-pinning brings the
24
+ * button straight back. That is a deliberate choice over tidying the file on save. Tidying means
25
+ * deciding "this pin is gone" from a list that might be stale, and one wrong answer deletes a
26
+ * promotion the user still wants, silently; four separate review findings on PR #1991 were all
27
+ * ways of getting that decision wrong. A key nobody can see costs a line of JSON.
28
+ *
29
+ * So the file's own limit is a sanity bound, not a budget — you would have to unpin forty-five
30
+ * promoted collections to meet it. */
31
+ export const MAX_STORED_TOOLBAR_PINS = 50;
32
+
33
+ /** How one promoted pin is named in the config: `"collection:works"`. */
34
+ export const toolbarPinKey = (pin: Pick<Shortcut, "kind" | "slug">): string => `${pin.kind}:${pin.slug}`;
35
+
36
+ // Split on the FIRST colon, not every colon: `kind` is one of two known words, so everything after
37
+ // it is the slug even if a slug ever contains one.
38
+ function parseToolbarPin(key: unknown): { kind: ShortcutKind; slug: string } | null {
39
+ if (typeof key !== "string") return null;
40
+ const separator = key.indexOf(":");
41
+ if (separator <= 0) return null;
42
+ const kind = SHORTCUT_KINDS.find((candidate) => candidate === key.slice(0, separator));
43
+ if (kind === undefined) return null;
44
+ const slug = key.slice(separator + 1);
45
+ return slug.length > 0 ? { kind, slug } : null;
46
+ }
47
+
48
+ /** Keep the well-formed keys, in order, deduped, up to the FILE's bound. Anything else — a config
49
+ * written by hand, a key naming a kind this build does not have — is dropped rather than rendered
50
+ * as a blank button. The bound here is `MAX_STORED_TOOLBAR_PINS`, not the number of buttons:
51
+ * truncating to five would delete keys whose pins are merely unpinned right now. */
52
+ export function sanitizeToolbarPins(input: unknown): string[] {
53
+ if (!Array.isArray(input)) return [];
54
+ const out: string[] = [];
55
+ input.forEach((raw) => {
56
+ const pin = parseToolbarPin(raw);
57
+ if (!pin) return;
58
+ const key = toolbarPinKey(pin);
59
+ if (out.includes(key) || out.length >= MAX_STORED_TOOLBAR_PINS) return;
60
+ out.push(key);
61
+ });
62
+ return out;
63
+ }
64
+
65
+ /** The pins the toolbar draws: the configured keys, in the CONFIG's order, resolved against the
66
+ * favourites that exist now, and cut to the number of buttons the row can carry.
67
+ *
68
+ * A key whose pin is gone — unpinned here, or in MulmoClaude, which writes the same file — is
69
+ * skipped rather than drawn: the title and icon come from the pin, so there is nothing to label it
70
+ * with. Skipped, not deleted, and it does not consume one of the five: re-pin it and the button is
71
+ * back exactly where it was.
72
+ *
73
+ * What counts as "gone" is gone from the list the CALLER holds. The client store reads
74
+ * `/api/shortcuts` once per page, so a pin removed by the other app is still drawn until something
75
+ * re-reads it — which opening Settings' Toolbar pins does (Codex, PR #1991). */
76
+ export function resolveToolbarPins(shortcuts: readonly Shortcut[], keys: readonly string[]): Shortcut[] {
77
+ const byKey = new Map(shortcuts.map((shortcut) => [toolbarPinKey(shortcut), shortcut]));
78
+ return keys
79
+ .flatMap((key) => {
80
+ const shortcut = byKey.get(key);
81
+ return shortcut ? [shortcut] : [];
82
+ })
83
+ .slice(0, MAX_TOOLBAR_PINS);
84
+ }
85
+
86
+ /** The list to save after promoting or demoting one pin.
87
+ *
88
+ * DEMOTING removes exactly the key the user unticked, and nothing else. PROMOTING appends, so a
89
+ * hand-ordered config is not reshuffled by ticking one more box. Neither ever drops a key that was
90
+ * not the subject of the click — see `MAX_STORED_TOOLBAR_PINS` for why that restraint is the whole
91
+ * design rather than an omission.
92
+ *
93
+ * `live` is what is pinned right now (`toolbarPinKey` of each favourite). It is read ONLY to count
94
+ * the promotions that can actually be drawn: a key whose pin is gone draws nothing, so counting it
95
+ * toward the five would fill the row with buttons that are not there and leave nothing to untick
96
+ * (Codex, PR #1991). Nothing is deleted on the strength of it, which is what makes a stale `live`
97
+ * harmless here — at worst the count is off by one until the next read.
98
+ *
99
+ * Returns the SAME array when nothing would change: already promoted, not promoted anyway, or a
100
+ * promotion refused because the row is full. */
101
+ export function nextToolbarPins(keys: readonly string[], live: readonly string[], key: string, promote: boolean): readonly string[] {
102
+ if (!promote) return keys.includes(key) ? keys.filter((entry) => entry !== key) : keys;
103
+ if (keys.includes(key)) return keys;
104
+ const drawn = keys.filter((entry) => live.includes(entry)).length;
105
+ if (drawn >= MAX_TOOLBAR_PINS || keys.length >= MAX_STORED_TOOLBAR_PINS) return keys;
106
+ return [...keys, key];
107
+ }