mulmoclaude 0.9.0 → 0.9.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.
- package/README.md +95 -38
- package/bin/mulmoclaude.js +1 -2
- package/client/assets/JsonEditor-CPJfn76E.css +1 -0
- package/client/assets/JsonEditor-CoWGJG3Y.js +10 -0
- package/client/assets/abnfDiagram-VRR7QNED-6nNByj6v-By7GJ3xW.js +1 -0
- package/client/assets/abnfDiagram-VRR7QNED-BrQlVixL.js +1 -0
- package/client/assets/arc-BXutyUAX.js +1 -0
- package/client/assets/arc-zT0wd74V-DulGJNnb.js +1 -0
- package/client/assets/architecture-TIHT7OUA-CYMWc3UT-C7HoFzWN.js +1 -0
- package/client/assets/architecture-TIHT7OUA-aml8u-G9.js +1 -0
- package/client/assets/architectureDiagram-ZJ3FMSHR-Bgnyaj_i-CCpEsCHN.js +36 -0
- package/client/assets/architectureDiagram-ZJ3FMSHR-DJTFpPjB.js +36 -0
- package/client/assets/array-BifhSqXX.js +1 -0
- package/client/assets/array-CApNbSU9-BifhSqXX.js +1 -0
- package/client/assets/blockDiagram-677ZJIJ3-DQ35o5E4-BXS11D3J.js +132 -0
- package/client/assets/blockDiagram-677ZJIJ3-aYvCODxv.js +132 -0
- package/client/assets/c4Diagram-LMCZKHZV-Bau6RMBb.js +10 -0
- package/client/assets/c4Diagram-LMCZKHZV-ClWZeiWo-DXGnOOUf.js +10 -0
- package/client/assets/channel-D9VSi_QV.js +1 -0
- package/client/assets/channel-Di5rtkx0-C3gZcytF.js +1 -0
- package/client/assets/chunk-2Q5K7J3B-C1jixKkw.js +1 -0
- package/client/assets/chunk-2Q5K7J3B-DfKpwyl9-DTxwf5E9.js +1 -0
- package/client/assets/chunk-32BRIVSS-CCt9wtYd-Cwa_IP35.js +1 -0
- package/client/assets/chunk-32BRIVSS-CxzHeys5.js +1 -0
- package/client/assets/chunk-52WLFC77-BtNjbTdw.js +10 -0
- package/client/assets/chunk-52WLFC77-FbBbR4uI-wsNC5d-f.js +10 -0
- package/client/assets/chunk-5VM5RSS4-BYnnUiN6-C4rOnu9A.js +15 -0
- package/client/assets/chunk-5VM5RSS4-ZNzvKenW.js +15 -0
- package/client/assets/chunk-7BUUIJ7U-Bb538aSH.js +1 -0
- package/client/assets/chunk-7BUUIJ7U-hh8aCuGX-mGEpPzQy.js +1 -0
- package/client/assets/chunk-C7G6YPKG-BE8ehsnc.js +1 -0
- package/client/assets/chunk-C7G6YPKG-C87hlS9c-BitbJpTa.js +1 -0
- package/client/assets/{chunk-D8eiyYIV-BY16KEZc.js → chunk-D8eiyYIV-BeeEsA1B.js} +1 -1
- package/client/assets/chunk-EX3LRPZG-Bhgskzyy.js +231 -0
- package/client/assets/chunk-EX3LRPZG-BqGqMXLN-Cl2Eo1_u.js +231 -0
- package/client/assets/chunk-FWX5IMBZ-BkbSAAuW-C2r4AuAO.js +2 -0
- package/client/assets/chunk-FWX5IMBZ-D9sskZb_.js +2 -0
- package/client/assets/chunk-HOUHSVGY-CBCXk7rb.js +1 -0
- package/client/assets/chunk-HOUHSVGY-Cxu0eDlh-owg_DcY0.js +1 -0
- package/client/assets/chunk-ICXQ74PX-DTqPpDQ9.js +2 -0
- package/client/assets/chunk-ICXQ74PX-hiraF_Xj-CdaEwVin.js +2 -0
- package/client/assets/chunk-JWPE2WC7-BQ3zXr2k-DSd3Ct95.js +1 -0
- package/client/assets/chunk-JWPE2WC7-DVXcaiue.js +1 -0
- package/client/assets/chunk-KEIR6QF5-DNzq6p3w.js +161 -0
- package/client/assets/chunk-MOJQB5TN-CqxshQHA-Cn_EpDyZ.js +88 -0
- package/client/assets/chunk-MOJQB5TN-D1s_zUGz.js +88 -0
- package/client/assets/chunk-OGEWGWER-CjCr7ceX-Dfk5-70y.js +1 -0
- package/client/assets/chunk-OGEWGWER-DzfhxoQd.js +1 -0
- package/client/assets/chunk-PUDLZKDR-BZlebNhn.js +156 -0
- package/client/assets/chunk-PUDLZKDR-Dx6M-vz1-CLyVurOz.js +156 -0
- package/client/assets/chunk-Q4XR5HBZ-BcJb7mwA.js +70 -0
- package/client/assets/chunk-Q4XR5HBZ-CZd-9lTB-qPEYoRNc.js +11 -0
- package/client/assets/chunk-RYQCIY6F-B_IgMG7T.js +1 -0
- package/client/assets/chunk-RYQCIY6F-YkbemkCt-CX4fncTQ.js +1 -0
- package/client/assets/chunk-V7JOEXUC-Buce04o6.js +206 -0
- package/client/assets/chunk-V7JOEXUC-rI0xlC_O-C607vJXy.js +206 -0
- package/client/assets/chunk-VAUOI2AC-CxhkzXN0.js +1 -0
- package/client/assets/chunk-VAUOI2AC-DrcykVNK-CSROJWdE.js +1 -0
- package/client/assets/chunk-VR4S4FIN-D5afNrsS.js +1 -0
- package/client/assets/chunk-VR4S4FIN-O6iF8Yvf-CLIwrgqU.js +1 -0
- package/client/assets/chunk-WYO6CB5R-B83L_z6I-Cs8mg9If.js +127 -0
- package/client/assets/chunk-WYO6CB5R-BXTIdTMw.js +125 -0
- package/client/assets/chunk-XXDRQBXY-DFXH_lWn-B5HjCp2c.js +1 -0
- package/client/assets/chunk-XXDRQBXY-DXMD6ofA.js +1 -0
- package/client/assets/chunk-Y2CYZVJY-Bdt8pFDJ-DsF7k-Jl.js +1 -0
- package/client/assets/chunk-Y2CYZVJY-DsF7k-Jl.js +1 -0
- package/client/assets/chunk-ZGVPDNZ5-BpFv9JSP-CHYySMjV.js +62 -0
- package/client/assets/chunk-ZGVPDNZ5-CYusI0J_.js +62 -0
- package/client/assets/chunk-ZIRB5QZD-Biy6ZNFG-D1maAiXb.js +32 -0
- package/client/assets/chunk-ZIRB5QZD-C6fEPe3t.js +32 -0
- package/client/assets/classDiagram-OUVF2IWQ-BgAZMSbT-BgxEE1D3.js +1 -0
- package/client/assets/classDiagram-OUVF2IWQ-CBD3Eidl.js +1 -0
- package/client/assets/classDiagram-v2-EOCWNBFH-CBD3Eidl.js +1 -0
- package/client/assets/classDiagram-v2-EOCWNBFH-DxHTyui1-BgxEE1D3.js +1 -0
- package/client/assets/cose-bilkent-JH36ORCC-CuSL2tA8-aMIqISfj.js +1 -0
- package/client/assets/cose-bilkent-JH36ORCC-WAJDBXv1.js +1 -0
- package/client/assets/cynefin-VYW2F7L2-DH2qkJKw.js +1 -0
- package/client/assets/cynefin-VYW2F7L2-DqA3n9nY-BNWhIK_n.js +1 -0
- package/client/assets/cynefinDiagram-TSTJHNR4-CUvPV9MV.js +62 -0
- package/client/assets/cynefinDiagram-TSTJHNR4-x0-0sQ15-Cq1gFcFu.js +62 -0
- package/client/assets/cytoscape.esm-CzdwbRaj-Djp6vQyU.js +321 -0
- package/client/assets/cytoscape.esm-Djp6vQyU.js +321 -0
- package/client/assets/dagre-CXRCoUWR.js +1 -0
- package/client/assets/dagre-VKFMJZFB-CQdfl-bx-CzZZuace.js +4 -0
- package/client/assets/dagre-VKFMJZFB-C_9IS7mB.js +4 -0
- package/client/assets/dagre-wczQIDso-CfevN9RO.js +1 -0
- package/client/assets/defaultLocale-C8Fc0cco.js +1 -0
- package/client/assets/defaultLocale-DUNguUWs-BbDo_yJX.js +1 -0
- package/client/assets/diagram-FQU43EPY-BOSB6VUb-isPdQ8WX.js +3 -0
- package/client/assets/diagram-FQU43EPY-BP8N00-b.js +3 -0
- package/client/assets/diagram-G47NLZAW-DLXrcXsN-BIqj7RKy.js +24 -0
- package/client/assets/diagram-G47NLZAW-ulE1JlWG.js +24 -0
- package/client/assets/diagram-NH7WQ7WH-BMQp1rkF-D6-fOq_v.js +24 -0
- package/client/assets/diagram-NH7WQ7WH-D_IXrL3i.js +24 -0
- package/client/assets/diagram-OA4YK3LP-C-pC6Eyu.js +30 -0
- package/client/assets/diagram-OA4YK3LP-D1wQ0vUj-BNi6QLCK.js +30 -0
- package/client/assets/diagram-WEI45ONY-Di9m35i-.js +41 -0
- package/client/assets/diagram-WEI45ONY-RR0DpF8R-BS3nrb3w.js +41 -0
- package/client/assets/dist-BQBs5pjy-BmtZ7Oc2.js +1 -0
- package/client/assets/dist-CQ3HaWOk.js +1 -0
- package/client/assets/ebnfDiagram-CCIWWBDH-Cbwnim2x.js +1 -0
- package/client/assets/ebnfDiagram-CCIWWBDH-M123uVJ8-9-dq55nQ.js +1 -0
- package/client/assets/erDiagram-Q63AITRT-BWx_-PXG-CWGfG4z5.js +85 -0
- package/client/assets/erDiagram-Q63AITRT-CMbtO3Sm.js +85 -0
- package/client/assets/eventmodeling-45OFAUF4--32SIpkL.js +1 -0
- package/client/assets/eventmodeling-45OFAUF4-_BVSjAXf-DQdL0Icr.js +1 -0
- package/client/assets/flowDiagram-23GEKE2U-BeOc_anm-CuVzKmmU.js +1 -0
- package/client/assets/flowDiagram-23GEKE2U-Dofa_qxG.js +1 -0
- package/client/assets/ganttDiagram-NO4QXBWP-BOoJ1eTw-TWoNmSvq.js +292 -0
- package/client/assets/ganttDiagram-NO4QXBWP-BgoAVKuc.js +292 -0
- package/client/assets/gitGraph-TEB2WS4Q-CH12KLTN-Bz4frAhV.js +1 -0
- package/client/assets/gitGraph-TEB2WS4Q-DIMvNvqt.js +1 -0
- package/client/assets/gitGraphDiagram-IHSO6WYX-B2CJhk_G-ClOKkjxw.js +106 -0
- package/client/assets/gitGraphDiagram-IHSO6WYX-Dmb6KnPz.js +106 -0
- package/client/assets/graphlib-B8gBHxth.js +1 -0
- package/client/assets/graphlib-hY-1btwe-DQjxxcnr.js +1 -0
- package/client/assets/{html2canvas-CDGcmOD3-CKJ6vKPo.js → html2canvas-CDGcmOD3-DRL9pFVl.js} +2 -2
- package/client/assets/{index-9lmYSaus.js → index-Dc0R-HW5.js} +129 -190
- package/client/assets/index-Dxo1Zdd-.css +2 -0
- package/client/assets/{index.es-DqtpmBm8-DFXjJgCa.js → index.es-DqtpmBm8-EQk3NgR8.js} +1 -1
- package/client/assets/info-DKCQHKI2-Cbw3mbiK-GsDZz9IO.js +1 -0
- package/client/assets/info-DKCQHKI2-ViCqobGo.js +1 -0
- package/client/assets/infoDiagram-FWYZ7A6U-HKV7LIG-.js +2 -0
- package/client/assets/infoDiagram-FWYZ7A6U-Mp1X3pBP-ATJv87Ur.js +2 -0
- package/client/assets/init-D6jRqBbL.js +1 -0
- package/client/assets/init-DEsX3bhM-D6jRqBbL.js +1 -0
- package/client/assets/ishikawaDiagram-FXEZZL3T-BNG7tkJu-CGCeOIlJ.js +70 -0
- package/client/assets/ishikawaDiagram-FXEZZL3T-CVDUj46f.js +70 -0
- package/client/assets/journeyDiagram-5HDEW3XC-BA-ESGLP.js +139 -0
- package/client/assets/journeyDiagram-5HDEW3XC-Dbp_hY9X-Bx-IoCe9.js +139 -0
- package/client/assets/kanban-definition-HUTT4EX6-Bqaet01L.js +89 -0
- package/client/assets/kanban-definition-HUTT4EX6-DSTc5u3q-C0JGckt1.js +89 -0
- package/client/assets/katex-CddkPoXu.js +257 -0
- package/client/assets/katex-M0IxphGf-CddkPoXu.js +257 -0
- package/client/assets/lib-4Tgx7rAy.js +114 -0
- package/client/assets/line-B1wBwzrY-f5wxpqoF.js +1 -0
- package/client/assets/line-CkyHfW7d.js +1 -0
- package/client/assets/linear-B9fuEF4c.js +1 -0
- package/client/assets/linear-lrGinF5_-CqNBync7.js +1 -0
- package/client/assets/map-CgrwEyH7-DWQFomlZ.js +1 -0
- package/client/assets/map-DsCK-0Cs.js +1 -0
- package/client/assets/marked.esm-CIU5FDdN.js +64 -0
- package/client/assets/marp-CSq0PPfK.js +3448 -0
- package/client/assets/material-symbols-outlined-DdRFxZLh.woff2 +0 -0
- package/client/assets/mermaid-parser.core-DC7NPJ_M-Ca6XzwfM.js +166 -0
- package/client/assets/mermaid-parser.core-SAwSf4_o.js +7 -0
- package/client/assets/mermaid.core-BBEQfkdJ.js +11 -0
- package/client/assets/mermaid.core-DZM3Ha-E-BxOo-RYG.js +11 -0
- package/client/assets/mindmap-definition-LN4V7U3C-B4BN0efL.js +96 -0
- package/client/assets/mindmap-definition-LN4V7U3C-DYtgcMsY-DynyLTNv.js +96 -0
- package/client/assets/ordinal-B9_Llu10-CU85fhaB.js +1 -0
- package/client/assets/ordinal-CopWnP7w.js +1 -0
- package/client/assets/packet-7NZHBO7P-CY4XRk9N.js +1 -0
- package/client/assets/packet-7NZHBO7P-lwb58iYx--vqSg65J.js +1 -0
- package/client/assets/path-BWPyau1x.js +1 -0
- package/client/assets/path-COivZ1Gw-C5riojK2.js +1 -0
- package/client/assets/pegDiagram-2B236MQR-BOKErjUu.js +1 -0
- package/client/assets/pegDiagram-2B236MQR-C43eIpKM-Bi_5Sxjt.js +1 -0
- package/client/assets/pie-RZYD4A2V-6BHB7bAx.js +1 -0
- package/client/assets/pie-RZYD4A2V-B1UWb4Gu-Dmn2hLWH.js +1 -0
- package/client/assets/pieDiagram-ENE6RG2P-BkTqgJyR-Bcdm19kR.js +39 -0
- package/client/assets/pieDiagram-ENE6RG2P-DtNjvz-U.js +39 -0
- package/client/assets/preload-helper-CZgWQFsJ.js +1 -0
- package/client/assets/purify.es-DY32g7DN.js +3 -0
- package/client/assets/quadrantDiagram-ABIIQ3AL-BQmAJL2v.js +7 -0
- package/client/assets/quadrantDiagram-ABIIQ3AL-Bm1Zjm45-B3orqx_f.js +7 -0
- package/client/assets/radar-I7S5WNFK-7CKcb_l--CqKdnR0v.js +1 -0
- package/client/assets/radar-I7S5WNFK-R7Jhjfdt.js +1 -0
- package/client/assets/railroad-3IZDKUUU-BepSPyHI.js +1 -0
- package/client/assets/railroad-3IZDKUUU-gCySKdnW-CKU26HTP.js +1 -0
- package/client/assets/railroad-abnf-AHOZXSZD-BlsHsp3c.js +1 -0
- package/client/assets/railroad-abnf-AHOZXSZD-BophH4r--CGkGHq54.js +1 -0
- package/client/assets/railroad-ebnf-EBAXGLYW-AZNjl_Zu-B7bn_PF3.js +1 -0
- package/client/assets/railroad-ebnf-EBAXGLYW-B4fMcGEH.js +1 -0
- package/client/assets/railroad-peg-LSFZ7HO6-B1ZrjJIu.js +1 -0
- package/client/assets/railroad-peg-LSFZ7HO6-BSiEEyeb-CP_B-N5g.js +1 -0
- package/client/assets/railroadDiagram-RFXS5EU6-8cz0Iby3.js +1 -0
- package/client/assets/railroadDiagram-RFXS5EU6-BkfbdeAs-Di6jH5I6.js +1 -0
- package/client/assets/requirementDiagram-TGXJPOKE-CrGTTjYg-DmIC7Shp.js +84 -0
- package/client/assets/requirementDiagram-TGXJPOKE-Dlqoe9TY.js +84 -0
- package/client/assets/rough.esm-CSKSodPl.js +1 -0
- package/client/assets/rough.esm-wVmwlXu7-CSKSodPl.js +1 -0
- package/client/assets/runtime-protocol-vue-WG3JLsjz.js +1 -0
- package/client/assets/runtime-vue-lHsY5zMj.js +1 -0
- package/client/assets/sankeyDiagram-HTMAVEWB-BkTKgu8Q.js +40 -0
- package/client/assets/sankeyDiagram-HTMAVEWB-rWXPf03Z-CjV9pCSJ.js +40 -0
- package/client/assets/{schemas-D_RbFtuQ.js → schemas-DGvl73AE.js} +3 -3
- package/client/assets/sequenceDiagram-DBY2YBRQ-BpuXUcFy.js +162 -0
- package/client/assets/sequenceDiagram-DBY2YBRQ-nkJYWO2m-BqS_sTCH.js +162 -0
- package/client/assets/sizeCapture-X5ZJPWSS-B0uUizjq.js +1 -0
- package/client/assets/sizeCapture-X5ZJPWSS-F5SadAuT-VHIb4_NU.js +1 -0
- package/client/assets/src-Brzfja-q-DNc4Or4z.js +1 -0
- package/client/assets/src-Cqa3Jy1j.js +1 -0
- package/client/assets/stateDiagram-2N3HPSRC-CdlGXuo7.js +1 -0
- package/client/assets/stateDiagram-2N3HPSRC-T4-clK8b-DQRiB4va.js +1 -0
- package/client/assets/stateDiagram-v2-6OUMAXLB-BN8Jr3ZM.js +1 -0
- package/client/assets/stateDiagram-v2-6OUMAXLB-DIp7nhRd-DbrOhUn_.js +1 -0
- package/client/assets/swimlanes-5IMT3BWC-HmQNEntu-CNBgZdur.js +2 -0
- package/client/assets/swimlanes-5IMT3BWC-iEJg5gki.js +2 -0
- package/client/assets/swimlanesDiagram-G3AALYLV-BeoZhwg7-DpqgeNRA.js +8 -0
- package/client/assets/swimlanesDiagram-G3AALYLV-o-sS6aEP.js +8 -0
- package/client/assets/timeline-definition-FHXFAJF6-Bjh-7QmL.js +120 -0
- package/client/assets/timeline-definition-FHXFAJF6-CNc9jSTP-BKNSymgY.js +120 -0
- package/client/assets/treeView-QDETBFTQ-D3cqRl6k.js +1 -0
- package/client/assets/treeView-QDETBFTQ-nIQcG1h9-O_ri9HxD.js +1 -0
- package/client/assets/treemap-6X3UGDF4-BnsvC8yL-Cfsty1e3.js +1 -0
- package/client/assets/treemap-6X3UGDF4-DGkZcvDG.js +1 -0
- package/client/assets/v4-B29ayslu.js +1 -0
- package/client/assets/vennDiagram-L72KCM5P-Cz3dbe7b.js +34 -0
- package/client/assets/vennDiagram-L72KCM5P-Dr3pTJ_0-C38xwcVo.js +34 -0
- package/client/assets/vue-C6d2VveO.js +1 -0
- package/client/assets/vue-i18n-CL-ejuNu.js +3 -0
- package/client/assets/vue.runtime.esm-bundler-BeoTfMNc.js +4 -0
- package/client/assets/wardley-OPB4EBWU-8Odxkx6V-CCDIVuBs.js +1 -0
- package/client/assets/wardley-OPB4EBWU-CZsCDvKk.js +1 -0
- package/client/assets/wardleyDiagram-EHGQE667-DSFc7ZZa-mlWXRiWx.js +78 -0
- package/client/assets/wardleyDiagram-EHGQE667-DiyoyO_X.js +78 -0
- package/client/assets/xychartDiagram-FW5EYKEG-BP0Nn4Pp-DRRiStGZ.js +7 -0
- package/client/assets/xychartDiagram-FW5EYKEG-DhY1_UGM.js +7 -0
- package/client/index.html +16 -12
- package/package.json +17 -19
- package/server/agent/backend/types.ts +1 -1
- package/server/agent/backgroundSessions.ts +43 -0
- package/server/agent/config.ts +3 -2
- package/server/agent/mcp-tools/manageCollection.ts +4 -4
- package/server/agent/sandboxMounts.ts +1 -1
- package/server/agent/stream.ts +1 -3
- package/server/api/auth/viewToken.ts +1 -1
- package/server/api/bridge/sessionRole.ts +47 -0
- package/server/api/routes/agent.ts +79 -2
- package/server/api/routes/collections.ts +60 -7
- package/server/api/routes/collectionsRegistry.ts +113 -0
- package/server/api/routes/dashboard.ts +47 -0
- package/server/api/routes/feeds.ts +2 -2
- package/server/api/routes/files.ts +1 -2
- package/server/api/routes/pdf.ts +48 -9
- package/server/api/routes/plugins.ts +2 -2
- package/server/api/routes/remoteHost.ts +55 -0
- package/server/api/routes/sessions.ts +20 -11
- package/server/api/routes/share.ts +105 -0
- package/server/api/routes/transcribe.ts +1 -1
- package/server/api/routes/wiki/history.ts +2 -2
- package/server/api/routes/wiki.ts +29 -177
- package/server/api/sandboxStatus.ts +1 -1
- package/server/build/dispatcher.mjs +25 -14928
- package/server/events/collection-change.ts +4 -4
- package/server/events/file-change.ts +2 -2
- package/server/events/scheduler-adapter.ts +3 -3
- package/server/events/task-manager/index.ts +3 -3
- package/server/index.ts +53 -17
- package/server/notifier/engine.ts +3 -3
- package/server/notifier/types.ts +2 -2
- package/server/plugins/html-builtin.ts +15 -1
- package/server/prompts/system/system.md +4 -0
- package/server/remoteHost/auth.ts +29 -0
- package/server/remoteHost/commandChannel.ts +56 -0
- package/server/remoteHost/firebase.ts +16 -0
- package/server/remoteHost/handlers/collectionPage.ts +29 -0
- package/server/remoteHost/handlers/getCollection.ts +32 -0
- package/server/remoteHost/handlers/getFeed.ts +35 -0
- package/server/remoteHost/handlers/index.ts +19 -0
- package/server/remoteHost/handlers/listCollections.ts +29 -0
- package/server/remoteHost/handlers/listFeeds.ts +37 -0
- package/server/remoteHost/handlers/listShortcuts.ts +24 -0
- package/server/remoteHost/handlers/startChat.ts +78 -0
- package/server/remoteHost/hostRunner.ts +122 -0
- package/server/remoteHost/index.ts +117 -0
- package/server/system/config.ts +1 -1
- package/server/system/credentials.ts +2 -3
- package/server/system/docker.ts +9 -8
- package/server/system/env.ts +8 -0
- package/server/system/whisper/index.ts +4 -4
- package/server/utils/claudeConfigPath.ts +57 -0
- package/server/utils/files/dashboard-io.ts +87 -0
- package/server/utils/files/session-io.ts +11 -0
- package/server/utils/markdown/frontmatter.ts +8 -4
- package/server/utils/regex.ts +4 -3
- package/server/utils/share/packHtml.ts +108 -0
- package/server/utils/share/rewriteAssets.ts +178 -0
- package/server/workspace/chat-index/paths.ts +8 -2
- package/server/workspace/chat-index/summarizer.ts +23 -15
- package/server/workspace/collections/configure.ts +5 -3
- package/server/workspace/collections/index.ts +8 -6
- package/server/workspace/collections/notifications.ts +3 -3
- package/server/workspace/collections/types.ts +2 -2
- package/server/workspace/collections/watcher.ts +2 -2
- package/server/workspace/feeds/configure.ts +24 -0
- package/server/workspace/hooks/handlers/skillBridge.ts +2 -2
- package/server/workspace/hooks/handlers/wikiSnapshot.ts +1 -1
- package/server/workspace/paths.ts +8 -1
- package/server/workspace/skills/catalog.ts +1 -3
- package/server/workspace/skills/external/catalog.ts +1 -3
- package/server/workspace/skills/paths.ts +4 -3
- package/server/workspace/skills/writer.ts +1 -4
- package/server/workspace/skills-preset.ts +3 -3
- package/server/workspace/wiki-pages/io.ts +2 -2
- package/server/workspace/workspace.ts +2 -2
- package/src/App.vue +41 -18
- package/src/components/ChatInput.vue +1 -1
- package/src/components/DashboardView.vue +372 -0
- package/src/components/FileContentRenderer.vue +32 -7
- package/src/components/JsonEditor.vue +1 -1
- package/src/components/PluginLauncher.vue +122 -79
- package/src/components/RemoteHostControl.vue +159 -0
- package/src/components/SessionCountBadges.vue +32 -0
- package/src/components/SessionHeaderControls.vue +1 -8
- package/src/components/SessionHistoryPanel.vue +2 -0
- package/src/components/SessionHistoryToggleButton.vue +1 -15
- package/src/components/SidebarHeader.vue +2 -0
- package/src/components/collectionTypes.ts +2 -2
- package/src/composables/accountingHost.ts +24 -0
- package/src/composables/collections/uiHost.ts +17 -2
- package/src/composables/useDashboard.ts +191 -0
- package/src/composables/useMarkdownZip.ts +51 -0
- package/src/composables/useNotifications.ts +1 -4
- package/src/composables/useSharePack.ts +60 -0
- package/src/composables/useTranslatedQueries.ts +15 -95
- package/src/composables/useTranslatedStrings.ts +68 -0
- package/src/composables/useVoiceInput.ts +3 -3
- package/src/config/apiRoutes.ts +57 -9
- package/src/config/createFilePolicy.ts +1 -1
- package/src/config/firebase.ts +12 -0
- package/src/config/firebaseConfig.ts +18 -0
- package/src/config/historyFilters.ts +7 -0
- package/src/config/pubsubChannels.ts +7 -27
- package/src/config/roles.ts +12 -10
- package/src/config/workspacePaths.ts +10 -0
- package/src/index.css +1 -1
- package/src/lang/de.ts +39 -220
- package/src/lang/en.ts +39 -191
- package/src/lang/es.ts +39 -218
- package/src/lang/fr.ts +39 -220
- package/src/lang/ja.ts +39 -216
- package/src/lang/ko.ts +39 -216
- package/src/lang/pt-BR.ts +39 -217
- package/src/lang/zh.ts +39 -215
- package/src/main.ts +3 -0
- package/src/plugins/accounting/definition.ts +12 -8
- package/src/plugins/accounting/index.ts +7 -4
- package/src/plugins/accounting/meta.ts +23 -60
- package/src/plugins/manageSkills/View.vue +9 -1
- package/src/plugins/photoLocations/View.vue +2 -2
- package/src/plugins/presentCollection/definition.ts +2 -2
- package/src/plugins/presentCollection/plugin.ts +2 -2
- package/src/plugins/presentCollection/types.ts +2 -2
- package/src/plugins/presentHtml/definition.ts +1 -1
- package/src/plugins/presentMulmoScript/View.vue +2 -2
- package/src/plugins/presentMulmoScript/helpers.ts +1 -1
- package/src/plugins/presentSVG/View.vue +2 -2
- package/src/plugins/scheduler/TasksTab.vue +1 -2
- package/src/plugins/skill/View.vue +7 -3
- package/src/plugins/spreadsheet/View.vue +3 -3
- package/src/plugins/spreadsheet/engine/functions/logical.ts +8 -1
- package/src/plugins/textResponse/View.vue +43 -16
- package/src/plugins/wiki/View.vue +29 -2
- package/src/plugins/wiki/components/WikiGraphView.vue +1 -1
- package/src/plugins/wiki/components/WikiPageBody.vue +4 -1
- package/src/plugins/wiki/helpers.ts +13 -81
- package/src/plugins/wiki/index.ts +1 -1
- package/src/router/guards.ts +1 -1
- package/src/router/index.ts +12 -0
- package/src/router/pageRoutes.ts +2 -0
- package/src/types/dashboard.ts +40 -0
- package/src/types/session.ts +3 -0
- package/src/utils/blobDownload.ts +19 -0
- package/src/utils/collections/presentSeed.ts +1 -1
- package/src/utils/html/customViewSrcdoc.ts +31 -3
- package/src/utils/html/previewCsp.ts +1 -1
- package/src/utils/id.ts +1 -1
- package/src/utils/image/htmlSrcAttrs.ts +1 -1
- package/src/utils/markdown/frontmatter.ts +12 -7
- package/src/utils/markdown/highlight.ts +22 -0
- package/src/utils/markdown/marpCustomSize.ts +2 -2
- package/src/utils/markdown/mermaidExtension.ts +56 -0
- package/src/utils/markdown/mermaidRender.ts +147 -0
- package/src/utils/markdown/setup.ts +17 -4
- package/src/utils/markdown/useMermaid.ts +33 -0
- package/src/utils/session/longRunning.ts +33 -0
- package/src/utils/session/mergeSessions.ts +1 -1
- package/client/assets/JsonEditor-Di5xGeZY.css +0 -1
- package/client/assets/JsonEditor-o5--tPQH.js +0 -10
- package/client/assets/index-tOu5ArRZ.css +0 -2
- package/client/assets/lib-D6Xy0IFc.js +0 -114
- package/client/assets/marp-D6GXA-EB.js +0 -3452
- package/client/assets/material-symbols-outlined-DtIK7AQn.woff2 +0 -0
- package/client/assets/runtime-protocol-vue-pU0Mw7Zm.js +0 -1
- package/client/assets/runtime-vue-fFYhnNg3.js +0 -1
- package/client/assets/vue-UDIWDtr8.js +0 -1
- package/client/assets/vue-i18n-CQbxVmNs.js +0 -3
- package/client/assets/vue.runtime.esm-bundler-BTyIdNAI.js +0 -4
- package/server/accounting/accountNormalize.ts +0 -32
- package/server/accounting/defaultAccounts.ts +0 -87
- package/server/accounting/eventPublisher.ts +0 -52
- package/server/accounting/journal.ts +0 -252
- package/server/accounting/openingBalances.ts +0 -114
- package/server/accounting/report.ts +0 -237
- package/server/accounting/service.ts +0 -718
- package/server/accounting/snapshotCache.ts +0 -334
- package/server/accounting/timeSeries.ts +0 -265
- package/server/accounting/types.ts +0 -148
- package/server/api/routes/accounting.ts +0 -373
- package/server/api/routes/wiki/frontmatter.ts +0 -34
- package/server/api/routes/wiki/pageIndex.ts +0 -53
- package/server/system/logs/aaa +0 -737
- package/server/system/logs/bb +0 -446
- package/server/utils/files/accounting-io.ts +0 -294
- package/server/workspace/feeds/engine.ts +0 -143
- package/server/workspace/feeds/fetch/httpClient.ts +0 -127
- package/server/workspace/feeds/fetch/rssParser.ts +0 -117
- package/server/workspace/feeds/index.ts +0 -8
- package/server/workspace/feeds/ingestTypes.ts +0 -62
- package/server/workspace/feeds/pathResolver.ts +0 -74
- package/server/workspace/feeds/paths.ts +0 -30
- package/server/workspace/feeds/projectItem.ts +0 -92
- package/server/workspace/feeds/registry.ts +0 -43
- package/server/workspace/feeds/retrievers/httpJson.ts +0 -19
- package/server/workspace/feeds/retrievers/index.ts +0 -27
- package/server/workspace/feeds/retrievers/registerAll.ts +0 -5
- package/server/workspace/feeds/retrievers/rss.ts +0 -24
- package/server/workspace/feeds/state.ts +0 -55
- package/src/composables/useAccountingChannel.ts +0 -58
- package/src/lib/wiki-page/graph.ts +0 -108
- package/src/lib/wiki-page/index-parse.ts +0 -221
- package/src/lib/wiki-page/link.ts +0 -62
- package/src/lib/wiki-page/lint.ts +0 -105
- package/src/lib/wiki-page/paths.ts +0 -35
- package/src/lib/wiki-page/slug.ts +0 -54
- package/src/plugins/accounting/Preview.vue +0 -103
- package/src/plugins/accounting/View.vue +0 -633
- package/src/plugins/accounting/actions.ts +0 -34
- package/src/plugins/accounting/api.ts +0 -301
- package/src/plugins/accounting/components/AccountEditor.vue +0 -250
- package/src/plugins/accounting/components/AccountRow.vue +0 -50
- package/src/plugins/accounting/components/AccountsList.vue +0 -102
- package/src/plugins/accounting/components/AccountsModal.vue +0 -301
- package/src/plugins/accounting/components/BalanceSheet.vue +0 -186
- package/src/plugins/accounting/components/BookSettings.vue +0 -284
- package/src/plugins/accounting/components/BookSwitcher.vue +0 -78
- package/src/plugins/accounting/components/DateRangePicker.vue +0 -140
- package/src/plugins/accounting/components/JournalEntryForm.vue +0 -505
- package/src/plugins/accounting/components/JournalList.vue +0 -554
- package/src/plugins/accounting/components/Ledger.vue +0 -206
- package/src/plugins/accounting/components/NewBookForm.vue +0 -211
- package/src/plugins/accounting/components/OpeningBalancesForm.vue +0 -272
- package/src/plugins/accounting/components/ProfitLoss.vue +0 -160
- package/src/plugins/accounting/components/accountDraft.ts +0 -13
- package/src/plugins/accounting/components/accountNumbering.ts +0 -103
- package/src/plugins/accounting/components/accountValidation.ts +0 -75
- package/src/plugins/accounting/components/useLatestRequest.ts +0 -44
- package/src/plugins/accounting/countries.ts +0 -158
- package/src/plugins/accounting/currencies.ts +0 -77
- package/src/plugins/accounting/dates.ts +0 -51
- package/src/plugins/accounting/fiscalYear.ts +0 -136
- package/src/plugins/accounting/timeSeriesEnums.ts +0 -16
- package/src/plugins/wiki/route.ts +0 -137
- /package/client/assets/{_plugin-vue_export-helper-B67ILkmu.js → _plugin-vue_export-helper-BDNMzG2s.js} +0 -0
- /package/client/assets/{purify.es-B27wDFIb-51iYcXuK.js → purify.es-B27wDFIb-Bu4Grnl0.js} +0 -0
- /package/client/assets/{typeof-DBp4T-Ny-z2wCIsir.js → typeof-DBp4T-Ny-B5XbjTb1.js} +0 -0
|
@@ -121,10 +121,7 @@ export interface DeleteSkillInput {
|
|
|
121
121
|
}
|
|
122
122
|
|
|
123
123
|
export type DeleteResult =
|
|
124
|
-
| { kind: "
|
|
125
|
-
| { kind: "invalid-slug"; slug: string }
|
|
126
|
-
| { kind: "not-found"; name: string }
|
|
127
|
-
| { kind: "user-scope"; name: string };
|
|
124
|
+
{ kind: "deleted"; name: string } | { kind: "invalid-slug"; slug: string } | { kind: "not-found"; name: string } | { kind: "user-scope"; name: string };
|
|
128
125
|
|
|
129
126
|
/**
|
|
130
127
|
* Remove a project-scope skill (SKILL.md + its containing folder).
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
// Host shim — the preset-skill sync logic + the bundled assets (helps + preset
|
|
2
|
-
// skills) now live in @mulmoclaude/workspace-setup (shared with MulmoTerminal so the
|
|
2
|
+
// skills) now live in @mulmoclaude/core/workspace-setup (shared with MulmoTerminal so the
|
|
3
3
|
// two apps seed a fresh workspace identically). Re-exported here so existing SERVER
|
|
4
4
|
// importers (the catalog, collections/configure) keep their import paths.
|
|
5
5
|
//
|
|
6
|
-
// NOTE for Vue/browser code: import `isPresetSlug` from "@mulmoclaude/workspace-setup/slug"
|
|
6
|
+
// NOTE for Vue/browser code: import `isPresetSlug` from "@mulmoclaude/core/workspace-setup/slug"
|
|
7
7
|
// (browser-safe) — NOT from here. This `.` re-export pulls in node:fs via the sync code.
|
|
8
8
|
export {
|
|
9
9
|
syncPresetSkills,
|
|
@@ -13,4 +13,4 @@ export {
|
|
|
13
13
|
type SyncPresetSkillsResult,
|
|
14
14
|
type SyncActivePresetSkillsOptions,
|
|
15
15
|
type SyncActivePresetSkillsResult,
|
|
16
|
-
} from "@mulmoclaude/workspace-setup";
|
|
16
|
+
} from "@mulmoclaude/core/workspace-setup";
|
|
@@ -23,8 +23,8 @@ import { mkdir, writeFile } from "node:fs/promises";
|
|
|
23
23
|
import { readTextSafe } from "../../utils/files/safe.js";
|
|
24
24
|
import { writeFileAtomic } from "../../utils/files/atomic.js";
|
|
25
25
|
import { mergeFrontmatter, parseFrontmatter, serializeWithFrontmatter } from "../../utils/markdown/frontmatter.js";
|
|
26
|
-
import { isSafeSlug } from "
|
|
27
|
-
import { wikiSlugFromAbsPath } from "
|
|
26
|
+
import { isSafeSlug } from "@mulmoclaude/core/wiki";
|
|
27
|
+
import { wikiSlugFromAbsPath } from "@mulmoclaude/core/wiki/paths";
|
|
28
28
|
import { workspacePath as defaultWorkspacePath } from "../workspace.js";
|
|
29
29
|
import { WORKSPACE_DIRS } from "../paths.js";
|
|
30
30
|
import { appendSnapshot } from "./snapshot.js";
|
|
@@ -5,9 +5,9 @@ import { log } from "../system/logger/index.js";
|
|
|
5
5
|
import { EAGER_WORKSPACE_DIRS, WORKSPACE_PATHS, workspacePath } from "./paths.js";
|
|
6
6
|
import { readWorkspaceTextSync, writeWorkspaceTextSync } from "../utils/files/workspace-io.js";
|
|
7
7
|
import { loadCustomDirs, ensureCustomDirs } from "./custom-dirs.js";
|
|
8
|
-
// Helps + preset skills + their sync logic now live in @mulmoclaude/workspace-setup
|
|
8
|
+
// Helps + preset skills + their sync logic now live in @mulmoclaude/core/workspace-setup
|
|
9
9
|
// (shared with MulmoTerminal); the bundled source dirs come from the package.
|
|
10
|
-
import { seedHelps, presetSkillsAssetDir, syncActivePresetSkills, syncPresetSkills } from "@mulmoclaude/workspace-setup";
|
|
10
|
+
import { seedHelps, presetSkillsAssetDir, syncActivePresetSkills, syncPresetSkills } from "@mulmoclaude/core/workspace-setup";
|
|
11
11
|
|
|
12
12
|
// Re-exported so existing callers (`import { workspacePath } from
|
|
13
13
|
// "./workspace.js"`) keep working. See workspace-paths.ts for the
|
package/src/App.vue
CHANGED
|
@@ -21,25 +21,28 @@
|
|
|
21
21
|
:active-tool-name="selectedResult?.toolName ?? null"
|
|
22
22
|
:active-view-mode="currentPage"
|
|
23
23
|
:shortcuts="shortcuts"
|
|
24
|
+
:active-session-count="activeSessionCount"
|
|
25
|
+
:unread-count="unreadCount"
|
|
24
26
|
@navigate="onPluginNavigate"
|
|
25
27
|
@navigate-shortcut="onShortcutNavigate"
|
|
28
|
+
@navigate-chat="handleHomeClick"
|
|
26
29
|
/>
|
|
27
30
|
</div>
|
|
28
31
|
</div>
|
|
29
|
-
<!-- Row 2: role selector + session tabs.
|
|
30
|
-
side panel is hidden
|
|
31
|
-
mutually exclusive.
|
|
32
|
-
|
|
32
|
+
<!-- Row 2: role selector + session tabs. Chat-only chrome — shown
|
|
33
|
+
on /chat whenever the side panel is hidden (Row 2 and the
|
|
34
|
+
side panel are mutually exclusive). Off /chat the whole row
|
|
35
|
+
is gone; the always-visible Chat button in Row 1 is the way
|
|
36
|
+
back into a conversation. The header-controls wrapper is
|
|
37
|
+
pinned to 264px (w-72 minus px-3 padding on each side) so
|
|
33
38
|
RoleSelector / + / toggle occupy the exact same x-range as
|
|
34
39
|
they do inside the open side panel — toggling the panel
|
|
35
40
|
therefore doesn't shift those controls. -->
|
|
36
|
-
<div v-if="!sidePanelVisible" class="flex items-center gap-2 px-3 py-2 border-b border-gray-100">
|
|
41
|
+
<div v-if="isChatPage && !sidePanelVisible" class="flex items-center gap-2 px-3 py-2 border-b border-gray-100">
|
|
37
42
|
<div class="w-[264px] shrink-0">
|
|
38
43
|
<SessionHeaderControls
|
|
39
44
|
:roles="roles"
|
|
40
45
|
:side-panel-visible="sidePanelVisible"
|
|
41
|
-
:active-session-count="activeSessionCount"
|
|
42
|
-
:unread-count="unreadCount"
|
|
43
46
|
@role-change="onRoleChange"
|
|
44
47
|
@new-session="handleNewSessionClick"
|
|
45
48
|
@update:side-panel-visible="setSidePanelVisible"
|
|
@@ -53,12 +56,12 @@
|
|
|
53
56
|
<div class="flex flex-1 min-h-0">
|
|
54
57
|
<!-- Session-history side panel. Opt-in column to the left of
|
|
55
58
|
the chat sidebar / canvas, toggled via
|
|
56
|
-
SessionHistoryToggleButton.
|
|
57
|
-
`sidePanelVisible` is true. Row 2 of the top bar
|
|
58
|
-
the panel is open — the panel's own header
|
|
59
|
-
role selector + new-session button instead. -->
|
|
59
|
+
SessionHistoryToggleButton. Chat-only chrome — renders on
|
|
60
|
+
/chat when `sidePanelVisible` is true. Row 2 of the top bar
|
|
61
|
+
hides when the panel is open — the panel's own header
|
|
62
|
+
supplies the role selector + new-session button instead. -->
|
|
60
63
|
<div
|
|
61
|
-
v-if="sidePanelVisible"
|
|
64
|
+
v-if="isChatPage && sidePanelVisible"
|
|
62
65
|
class="relative border-r border-gray-200 bg-white text-gray-900 flex flex-col min-w-0 overflow-hidden"
|
|
63
66
|
:class="sidePanelExpanded ? 'flex-1' : 'w-72 flex-shrink-0'"
|
|
64
67
|
data-testid="session-history-side-panel"
|
|
@@ -72,8 +75,6 @@
|
|
|
72
75
|
<SessionHeaderControls
|
|
73
76
|
:roles="roles"
|
|
74
77
|
:side-panel-visible="sidePanelVisible"
|
|
75
|
-
:active-session-count="activeSessionCount"
|
|
76
|
-
:unread-count="unreadCount"
|
|
77
78
|
@role-change="onRoleChange"
|
|
78
79
|
@new-session="handleNewSessionClick"
|
|
79
80
|
@update:side-panel-visible="setSidePanelVisibleAndCollapse"
|
|
@@ -211,6 +212,11 @@
|
|
|
211
212
|
variants; standalone routes are wrapped here against the
|
|
212
213
|
same `pkg-name + endpoints` pair so the `useRuntime()`
|
|
213
214
|
call resolves. -->
|
|
215
|
+
<!-- Dashboard — grid of favorite (pinned) collections, each a
|
|
216
|
+
live embedded view. Host component (no PluginScopedRoot):
|
|
217
|
+
CollectionView talks to /api/collections directly, same as
|
|
218
|
+
the collections index below. -->
|
|
219
|
+
<DashboardView v-else-if="currentPage === 'dashboard'" />
|
|
214
220
|
<FilesView v-else-if="currentPage === 'files'" :refresh-token="filesRefreshToken" @load-session="handleSessionSelect" />
|
|
215
221
|
<PluginScopedRoot v-else-if="currentPage === 'automations'" pkg-name="scheduler" :endpoints="API_ROUTES.scheduler">
|
|
216
222
|
<AutomationsView />
|
|
@@ -228,6 +234,13 @@
|
|
|
228
234
|
directly. -->
|
|
229
235
|
<CollectionView v-else-if="currentPage === 'collections' && route.params.slug" :key="String(route.params.slug)" />
|
|
230
236
|
<CollectionsIndexView v-else-if="currentPage === 'collections'" />
|
|
237
|
+
<!-- Accounting — the double-entry bookkeeping app. Host
|
|
238
|
+
component (no PluginScopedRoot): the View talks to
|
|
239
|
+
/api/accounting via the host's apiCall directly and never
|
|
240
|
+
calls `useRuntime()`, so it needs no plugin scope. Mounted
|
|
241
|
+
without a `selected-result` prop — standalone it self-fetches
|
|
242
|
+
the book list and auto-selects a book on mount. -->
|
|
243
|
+
<AccountingView v-else-if="currentPage === 'accounting'" />
|
|
231
244
|
<!-- Debug page (encore plan PR 1 follow-up). The View ships
|
|
232
245
|
inside the @mulmoclaude/debug-plugin runtime package; we
|
|
233
246
|
look it up by tool name and render the registered
|
|
@@ -301,7 +314,7 @@
|
|
|
301
314
|
<!-- Global confirm dialog. Renders the module-global confirm state opened
|
|
302
315
|
via useConfirm()/the collection plugin's confirm() capability — mounted
|
|
303
316
|
here at the app root so it survives any single view (CollectionView used
|
|
304
|
-
to render its own before moving into @mulmoclaude/collection
|
|
317
|
+
to render its own before moving into @mulmoclaude/core/collection). -->
|
|
305
318
|
<ConfirmModal />
|
|
306
319
|
</div>
|
|
307
320
|
</template>
|
|
@@ -324,11 +337,13 @@ import SessionHistoryPanel from "./components/SessionHistoryPanel.vue";
|
|
|
324
337
|
import SessionSidebar from "./components/SessionSidebar.vue";
|
|
325
338
|
import ThinkingIndicator from "./components/ThinkingIndicator.vue";
|
|
326
339
|
import PluginLauncher from "./components/PluginLauncher.vue";
|
|
340
|
+
import DashboardView from "./components/DashboardView.vue";
|
|
327
341
|
import StackView from "./components/StackView.vue";
|
|
328
342
|
import FilesView from "./components/FilesView.vue";
|
|
329
343
|
import AutomationsView from "./plugins/scheduler/AutomationsView.vue";
|
|
330
344
|
import WikiView from "./plugins/wiki/View.vue";
|
|
331
|
-
import {
|
|
345
|
+
import { AccountingView } from "@mulmoclaude/accounting-plugin/vue";
|
|
346
|
+
import { buildWikiRouteParams } from "@mulmoclaude/core/wiki";
|
|
332
347
|
import { CollectionView, CollectionsIndexView, FeedsView } from "@mulmoclaude/collection-plugin/vue";
|
|
333
348
|
import PluginScopedRoot from "./components/PluginScopedRoot.vue";
|
|
334
349
|
import SettingsModal from "./components/SettingsModal.vue";
|
|
@@ -371,7 +386,7 @@ import ConfirmModal from "./components/ConfirmModal.vue";
|
|
|
371
386
|
import { useNotifications } from "./composables/useNotifications";
|
|
372
387
|
import { collectionNotifiedSeverities } from "./utils/collections/notifiedItems";
|
|
373
388
|
import { installCollectionAppBindings } from "./composables/collections/uiHost";
|
|
374
|
-
import type { CollectionsListResponse } from "@mulmoclaude/collection
|
|
389
|
+
import type { CollectionsListResponse } from "@mulmoclaude/core/collection";
|
|
375
390
|
import { useHealth } from "./composables/useHealth";
|
|
376
391
|
import { useSessionHistory } from "./composables/useSessionHistory";
|
|
377
392
|
import { useRightSidebar } from "./composables/useRightSidebar";
|
|
@@ -588,7 +603,7 @@ const showSettings = ref(false);
|
|
|
588
603
|
// When the Settings modal closes, re-check voice-input availability: the
|
|
589
604
|
// user may have just enabled it / started the model download, and the
|
|
590
605
|
// mic button should appear without a reload (the composable then polls
|
|
591
|
-
// until the download finishes). See plans/feat-voice-input.md.
|
|
606
|
+
// until the download finishes). See plans/done/feat-voice-input.md.
|
|
592
607
|
function onSettingsOpenChange(open: boolean): void {
|
|
593
608
|
showSettings.value = open;
|
|
594
609
|
if (!open) chatInputRef.value?.refreshVoiceAvailability()?.catch(() => undefined);
|
|
@@ -678,10 +693,18 @@ const canvasDropHandlers = computed(() =>
|
|
|
678
693
|
// don't persist empty sessions on the server. Fires true → false only;
|
|
679
694
|
// an empty → /chat transition is handled by the route-params watcher
|
|
680
695
|
// and onMounted.
|
|
696
|
+
//
|
|
697
|
+
// Also collapse the side panel's transient full-width mode. The panel
|
|
698
|
+
// itself is chat-only chrome (`isChatPage && sidePanelVisible`), so it
|
|
699
|
+
// unmounts off /chat — but the canvas/sidebar stay gated by
|
|
700
|
+
// `!sidePanelExpanded`. Without this reset, leaving /chat while expanded
|
|
701
|
+
// would unmount the panel AND keep the canvas hidden, blanking plugin
|
|
702
|
+
// pages until the panel is collapsed again.
|
|
681
703
|
watch(isChatPage, (isChat, wasChat) => {
|
|
682
704
|
if (!(wasChat && !isChat)) return;
|
|
683
705
|
removeCurrentIfEmpty();
|
|
684
706
|
currentSessionId.value = "";
|
|
707
|
+
sidePanelExpanded.value = false;
|
|
685
708
|
});
|
|
686
709
|
|
|
687
710
|
function handleSessionSelect(sessionId: string): void {
|
|
@@ -161,7 +161,7 @@ const emit = defineEmits<{
|
|
|
161
161
|
|
|
162
162
|
// Local voice input (Mac-only). The mic button is hidden unless the
|
|
163
163
|
// backend reports voice input ready; transcripts are appended to the
|
|
164
|
-
// input for review, never auto-sent. See plans/feat-voice-input.md.
|
|
164
|
+
// input for review, never auto-sent. See plans/done/feat-voice-input.md.
|
|
165
165
|
function insertTranscript(text: string): void {
|
|
166
166
|
const current = props.modelValue;
|
|
167
167
|
const next = current.trim().length > 0 ? `${current.trimEnd()} ${text}` : text;
|
|
@@ -0,0 +1,372 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="h-full overflow-auto p-4" :class="{ 'select-none': isResizing || isReordering }" data-testid="dashboard-view">
|
|
3
|
+
<!-- Empty state: no pinned collections yet. -->
|
|
4
|
+
<div v-if="favoriteSlugs.length === 0" class="h-full flex flex-col items-center justify-center text-center text-slate-500 gap-2">
|
|
5
|
+
<span class="material-icons text-4xl text-slate-300">dashboard</span>
|
|
6
|
+
<p class="text-sm font-medium">{{ t("dashboard.empty") }}</p>
|
|
7
|
+
<p class="text-xs">{{ t("dashboard.emptyHint") }}</p>
|
|
8
|
+
</div>
|
|
9
|
+
|
|
10
|
+
<!-- Two-column grid of favorite collections. Each tile shows a live
|
|
11
|
+
embedded CollectionView in the tile's chosen view mode. Height is
|
|
12
|
+
per grid ROW (both side-by-side tiles share it), set via the
|
|
13
|
+
resize handle and stored positionally in the layout. -->
|
|
14
|
+
<div v-else class="grid gap-4 sm:grid-cols-2" data-testid="dashboard-grid">
|
|
15
|
+
<section
|
|
16
|
+
v-for="(tile, index) in tiles"
|
|
17
|
+
v-show="metaFor(tile.slug)"
|
|
18
|
+
:key="tile.slug"
|
|
19
|
+
class="flex flex-col border border-slate-200 rounded-lg bg-white overflow-hidden shadow-sm transition-opacity"
|
|
20
|
+
:class="{ 'ring-2 ring-indigo-400': dropIndex === index && dragIndex !== index, 'opacity-50': dragIndex === index }"
|
|
21
|
+
:data-dashboard-index="index"
|
|
22
|
+
:data-testid="`dashboard-tile-${tile.slug}`"
|
|
23
|
+
>
|
|
24
|
+
<!-- Tile header: drag handle + title/icon (opens full view) + view picker. -->
|
|
25
|
+
<header class="flex items-center gap-2 px-3 py-2 border-b border-slate-100 bg-slate-50">
|
|
26
|
+
<span
|
|
27
|
+
class="material-icons text-base text-slate-400 cursor-grab select-none touch-none"
|
|
28
|
+
:title="t('dashboard.dragHint')"
|
|
29
|
+
:aria-label="t('dashboard.dragHint')"
|
|
30
|
+
data-testid="dashboard-tile-drag"
|
|
31
|
+
@pointerdown.prevent="onReorderStart(index, $event)"
|
|
32
|
+
>drag_indicator</span
|
|
33
|
+
>
|
|
34
|
+
<button
|
|
35
|
+
type="button"
|
|
36
|
+
class="flex items-center gap-1.5 min-w-0 flex-1 text-left text-sm font-semibold text-slate-700 hover:text-indigo-600"
|
|
37
|
+
:title="t('dashboard.openFull')"
|
|
38
|
+
@click="onTitleActivate($event, tile.slug)"
|
|
39
|
+
@dblclick="openFull(tile.slug)"
|
|
40
|
+
>
|
|
41
|
+
<span class="material-symbols-outlined text-base flex-none">{{ metaFor(tile.slug)?.icon || "apps" }}</span>
|
|
42
|
+
<span class="truncate">{{ metaFor(tile.slug)?.title || tile.slug }}</span>
|
|
43
|
+
</button>
|
|
44
|
+
<select
|
|
45
|
+
class="flex-none text-xs bg-white border border-slate-200 rounded px-1.5 py-1 text-slate-600 focus:outline-none focus:ring-2 focus:ring-indigo-500/20"
|
|
46
|
+
:value="effectiveView(tile)"
|
|
47
|
+
:aria-label="t('dashboard.viewPickerLabel')"
|
|
48
|
+
:data-testid="`dashboard-tile-view-${tile.slug}`"
|
|
49
|
+
@change="onPickView(tile.slug, $event)"
|
|
50
|
+
>
|
|
51
|
+
<option v-for="mode in modesFor(tile.slug)" :key="mode" :value="mode">{{ modeLabel(tile.slug, mode) }}</option>
|
|
52
|
+
</select>
|
|
53
|
+
</header>
|
|
54
|
+
<!-- Live embedded view. `:key` remounts the view when the mode
|
|
55
|
+
changes so the new initial view takes effect. Height is the
|
|
56
|
+
grid ROW's height (shared by both side-by-side tiles), set via
|
|
57
|
+
the resize handle below. -->
|
|
58
|
+
<div class="overflow-auto" :style="{ height: `${rowHeight(index)}px` }">
|
|
59
|
+
<CollectionView
|
|
60
|
+
:key="`${tile.slug}:${effectiveView(tile)}`"
|
|
61
|
+
:slug="tile.slug"
|
|
62
|
+
:initial-view="effectiveView(tile)"
|
|
63
|
+
hide-view-toggle
|
|
64
|
+
hide-header
|
|
65
|
+
hide-search
|
|
66
|
+
/>
|
|
67
|
+
</div>
|
|
68
|
+
<!-- Resize handle: drag vertically to set this row's height. -->
|
|
69
|
+
<div
|
|
70
|
+
class="h-2 flex-none cursor-row-resize bg-slate-100 hover:bg-indigo-200 transition-colors flex items-center justify-center"
|
|
71
|
+
:title="t('dashboard.resizeHint')"
|
|
72
|
+
:aria-label="t('dashboard.resizeHint')"
|
|
73
|
+
:data-testid="`dashboard-tile-resize-${tile.slug}`"
|
|
74
|
+
@pointerdown.prevent="onResizeStart(index, $event)"
|
|
75
|
+
>
|
|
76
|
+
<span class="h-0.5 w-8 rounded-full bg-slate-300"></span>
|
|
77
|
+
</div>
|
|
78
|
+
</section>
|
|
79
|
+
</div>
|
|
80
|
+
</div>
|
|
81
|
+
</template>
|
|
82
|
+
|
|
83
|
+
<script setup lang="ts">
|
|
84
|
+
import { computed, onBeforeUnmount, onMounted, ref, watch } from "vue";
|
|
85
|
+
import { useI18n } from "vue-i18n";
|
|
86
|
+
import { useRouter } from "vue-router";
|
|
87
|
+
import { CollectionView, applicableViewModes, customViewKey, type CollectionViewMode } from "@mulmoclaude/collection-plugin/vue";
|
|
88
|
+
import { useShortcuts } from "../composables/useShortcuts";
|
|
89
|
+
import { useDashboard } from "../composables/useDashboard";
|
|
90
|
+
import { apiGet } from "../utils/api";
|
|
91
|
+
import { API_ROUTES } from "../config/apiRoutes";
|
|
92
|
+
import { PAGE_ROUTES } from "../router/pageRoutes";
|
|
93
|
+
import type { DashboardTile } from "../types/dashboard";
|
|
94
|
+
import type { CollectionDetailResponse, CollectionSchema } from "./collectionTypes";
|
|
95
|
+
|
|
96
|
+
const { t } = useI18n();
|
|
97
|
+
const router = useRouter();
|
|
98
|
+
|
|
99
|
+
const { shortcuts, load: loadShortcuts } = useShortcuts();
|
|
100
|
+
const { tiles, rowHeights, load: loadDashboard, reconcile, setTiles, setViewMode, setRowHeight } = useDashboard();
|
|
101
|
+
|
|
102
|
+
// Favorites = pinned COLLECTION shortcuts (feeds are excluded — the
|
|
103
|
+
// dashboard is a collections surface). The cached title/icon ride along
|
|
104
|
+
// so a tile renders without an extra fetch.
|
|
105
|
+
const collectionFavorites = computed(() => shortcuts.value.filter((entry) => entry.kind === "collection"));
|
|
106
|
+
const favoriteSlugs = computed(() => collectionFavorites.value.map((entry) => entry.slug));
|
|
107
|
+
const metaFor = (slug: string) => collectionFavorites.value.find((entry) => entry.slug === slug);
|
|
108
|
+
|
|
109
|
+
// Per-slug schema, fetched lazily to drive each tile's view picker.
|
|
110
|
+
const schemas = ref<Record<string, CollectionSchema>>({});
|
|
111
|
+
|
|
112
|
+
async function loadSchema(slug: string): Promise<void> {
|
|
113
|
+
if (schemas.value[slug]) return;
|
|
114
|
+
const url = API_ROUTES.collections.detail.replace(":slug", encodeURIComponent(slug));
|
|
115
|
+
const result = await apiGet<CollectionDetailResponse>(url);
|
|
116
|
+
if (result.ok) schemas.value = { ...schemas.value, [slug]: result.data.collection.schema };
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function modesFor(slug: string): CollectionViewMode[] {
|
|
120
|
+
const schema = schemas.value[slug];
|
|
121
|
+
return schema ? applicableViewModes(schema) : ["table"];
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/** The view a tile opens in: its stored mode when still applicable, else
|
|
125
|
+
* the first available mode (table). */
|
|
126
|
+
function effectiveView(tile: DashboardTile): CollectionViewMode {
|
|
127
|
+
const modes = modesFor(tile.slug);
|
|
128
|
+
return tile.viewMode && modes.includes(tile.viewMode as CollectionViewMode) ? (tile.viewMode as CollectionViewMode) : (modes[0] ?? "table");
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const BUILTIN_LABEL_KEYS: Record<string, string> = {
|
|
132
|
+
table: "dashboard.viewTable",
|
|
133
|
+
calendar: "dashboard.viewCalendar",
|
|
134
|
+
kanban: "dashboard.viewKanban",
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
function modeLabel(slug: string, mode: CollectionViewMode): string {
|
|
138
|
+
const builtin = BUILTIN_LABEL_KEYS[mode];
|
|
139
|
+
if (builtin) return t(builtin);
|
|
140
|
+
// Custom view: use the author-authored label from the schema.
|
|
141
|
+
const view = schemas.value[slug]?.views?.find((entry) => customViewKey(entry.id) === mode);
|
|
142
|
+
return view?.label ?? mode;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function onPickView(slug: string, event: Event): void {
|
|
146
|
+
const { value } = event.target as HTMLSelectElement;
|
|
147
|
+
void setViewMode(slug, value);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function openFull(slug: string): void {
|
|
151
|
+
router.push({ name: PAGE_ROUTES.collections, params: { slug } }).catch(() => {});
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Keyboard (Enter/Space on a native <button>) and assistive-tech "activate"
|
|
155
|
+
// both dispatch a click with detail 0, whereas a real mouse click has
|
|
156
|
+
// detail >= 1. Act only on the former so keyboard/AT users can open the full
|
|
157
|
+
// view, while a plain mouse single-click does NOT navigate — only the
|
|
158
|
+
// double-click does (the requested pointer UX).
|
|
159
|
+
function onTitleActivate(event: MouseEvent, slug: string): void {
|
|
160
|
+
if (event.detail === 0) openFull(slug);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// ── Drag-to-reorder via pointer events. Native HTML5 DnD conflicted with
|
|
164
|
+
// the embedded views' own card drag-and-drop (e.g. kanban), so drops
|
|
165
|
+
// over a tile body were swallowed — pointer + elementFromPoint avoids
|
|
166
|
+
// that entirely, the same robust approach as the row resize. ──
|
|
167
|
+
const dragIndex = ref<number | null>(null);
|
|
168
|
+
const dropIndex = ref<number | null>(null);
|
|
169
|
+
const isReordering = ref(false);
|
|
170
|
+
let reorder: { startIndex: number; handle: HTMLElement; pointerId: number } | null = null;
|
|
171
|
+
|
|
172
|
+
/** The tile index under a viewport point, or null when over no tile. */
|
|
173
|
+
function tileIndexAtPoint(clientX: number, clientY: number): number | null {
|
|
174
|
+
const section = document.elementFromPoint(clientX, clientY)?.closest<HTMLElement>("[data-dashboard-index]");
|
|
175
|
+
if (!section) return null;
|
|
176
|
+
const index = Number(section.dataset.dashboardIndex);
|
|
177
|
+
return Number.isNaN(index) ? null : index;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function onReorderMove(event: PointerEvent): void {
|
|
181
|
+
if (!reorder) return;
|
|
182
|
+
event.preventDefault();
|
|
183
|
+
dropIndex.value = tileIndexAtPoint(event.clientX, event.clientY);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function teardownReorder(): void {
|
|
187
|
+
window.removeEventListener("pointermove", onReorderMove);
|
|
188
|
+
window.removeEventListener("pointerup", onReorderEnd);
|
|
189
|
+
window.removeEventListener("pointercancel", onReorderEnd);
|
|
190
|
+
isReordering.value = false;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function onReorderEnd(): void {
|
|
194
|
+
teardownReorder();
|
|
195
|
+
if (!reorder) return;
|
|
196
|
+
const { startIndex, handle, pointerId } = reorder;
|
|
197
|
+
const target = dropIndex.value;
|
|
198
|
+
reorder = null;
|
|
199
|
+
dragIndex.value = null;
|
|
200
|
+
dropIndex.value = null;
|
|
201
|
+
try {
|
|
202
|
+
handle.releasePointerCapture(pointerId);
|
|
203
|
+
} catch {
|
|
204
|
+
// Capture may already be gone (pointercancel) — ignore.
|
|
205
|
+
}
|
|
206
|
+
if (target === null || target === startIndex) return;
|
|
207
|
+
const next = [...tiles.value];
|
|
208
|
+
const [moved] = next.splice(startIndex, 1);
|
|
209
|
+
next.splice(target, 0, moved);
|
|
210
|
+
void setTiles(next);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function onReorderStart(index: number, event: PointerEvent): void {
|
|
214
|
+
const handle = event.currentTarget as HTMLElement;
|
|
215
|
+
try {
|
|
216
|
+
handle.setPointerCapture(event.pointerId);
|
|
217
|
+
} catch {
|
|
218
|
+
// Pointer capture unsupported — fall back to plain window listeners.
|
|
219
|
+
}
|
|
220
|
+
reorder = { startIndex: index, handle, pointerId: event.pointerId };
|
|
221
|
+
dragIndex.value = index;
|
|
222
|
+
isReordering.value = true;
|
|
223
|
+
window.addEventListener("pointermove", onReorderMove);
|
|
224
|
+
window.addEventListener("pointerup", onReorderEnd);
|
|
225
|
+
window.addEventListener("pointercancel", onReorderEnd);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// ── Drag-to-resize, per grid ROW (height is positional, not per tile) ──
|
|
229
|
+
const DEFAULT_HEIGHT = 320;
|
|
230
|
+
const MIN_HEIGHT = 160;
|
|
231
|
+
const MAX_HEIGHT = 900;
|
|
232
|
+
|
|
233
|
+
// Column count tracks the grid's responsive breakpoint (`sm:grid-cols-2`):
|
|
234
|
+
// 1 column below 640px, 2 at/above. Row grouping must follow it so a
|
|
235
|
+
// stacked tile in the single-column layout resizes on its own row instead
|
|
236
|
+
// of sharing a height with its desktop pair.
|
|
237
|
+
const SM_BREAKPOINT = "(min-width: 640px)";
|
|
238
|
+
const columns = ref(2);
|
|
239
|
+
let columnQuery: MediaQueryList | null = null;
|
|
240
|
+
|
|
241
|
+
function syncColumns(): void {
|
|
242
|
+
columns.value = columnQuery?.matches ? 2 : 1;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
function rowOf(index: number): number {
|
|
246
|
+
return Math.floor(index / columns.value);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// Live height during a drag (smooth, not yet persisted), keyed by
|
|
250
|
+
// `"<columns>:<row>"` so an in-flight value can't bleed across layouts.
|
|
251
|
+
const liveRowHeights = ref<Record<string, number>>({});
|
|
252
|
+
|
|
253
|
+
function liveKey(cols: number, row: number): string {
|
|
254
|
+
return `${cols}:${row}`;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
function rowHeight(index: number): number {
|
|
258
|
+
const row = rowOf(index);
|
|
259
|
+
const live = liveRowHeights.value[liveKey(columns.value, row)];
|
|
260
|
+
if (live !== undefined) return live;
|
|
261
|
+
const stored = rowHeights.value[String(columns.value)]?.[row];
|
|
262
|
+
return stored && stored > 0 ? stored : DEFAULT_HEIGHT;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// True while a drag is in progress — disables text selection so the drag
|
|
266
|
+
// doesn't accidentally select tile content.
|
|
267
|
+
const isResizing = ref(false);
|
|
268
|
+
|
|
269
|
+
interface ResizeState {
|
|
270
|
+
cols: number;
|
|
271
|
+
row: number;
|
|
272
|
+
startY: number;
|
|
273
|
+
startHeight: number;
|
|
274
|
+
handle: HTMLElement;
|
|
275
|
+
pointerId: number;
|
|
276
|
+
}
|
|
277
|
+
let resize: ResizeState | null = null;
|
|
278
|
+
// Latest pointer Y, applied once per frame (rAF-coalesced) so a flood of
|
|
279
|
+
// pointermove events can't thrash re-renders of the embedded views.
|
|
280
|
+
let pendingY: number | null = null;
|
|
281
|
+
let rafId = 0;
|
|
282
|
+
|
|
283
|
+
function applyResize(): void {
|
|
284
|
+
rafId = 0;
|
|
285
|
+
if (!resize || pendingY === null) return;
|
|
286
|
+
const next = Math.min(MAX_HEIGHT, Math.max(MIN_HEIGHT, resize.startHeight + (pendingY - resize.startY)));
|
|
287
|
+
liveRowHeights.value = { ...liveRowHeights.value, [liveKey(resize.cols, resize.row)]: next };
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
function onResizeMove(event: PointerEvent): void {
|
|
291
|
+
if (!resize) return;
|
|
292
|
+
event.preventDefault();
|
|
293
|
+
pendingY = event.clientY;
|
|
294
|
+
if (!rafId) rafId = requestAnimationFrame(applyResize);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
function clearLiveRow(cols: number, row: number): void {
|
|
298
|
+
const key = liveKey(cols, row);
|
|
299
|
+
liveRowHeights.value = Object.fromEntries(Object.entries(liveRowHeights.value).filter(([entry]) => entry !== key));
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// Persist the final height, then drop the live override so the stored
|
|
303
|
+
// value (now identical) takes over without a flash.
|
|
304
|
+
async function persistRowHeight(cols: number, row: number, height: number): Promise<void> {
|
|
305
|
+
await setRowHeight(cols, row, Math.round(height));
|
|
306
|
+
clearLiveRow(cols, row);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
function teardownResize(): void {
|
|
310
|
+
window.removeEventListener("pointermove", onResizeMove);
|
|
311
|
+
window.removeEventListener("pointerup", onResizeEnd);
|
|
312
|
+
window.removeEventListener("pointercancel", onResizeEnd);
|
|
313
|
+
if (rafId) cancelAnimationFrame(rafId);
|
|
314
|
+
rafId = 0;
|
|
315
|
+
pendingY = null;
|
|
316
|
+
isResizing.value = false;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
function onResizeEnd(): void {
|
|
320
|
+
teardownResize();
|
|
321
|
+
if (!resize) return;
|
|
322
|
+
const { cols, row, handle, pointerId } = resize;
|
|
323
|
+
resize = null;
|
|
324
|
+
try {
|
|
325
|
+
handle.releasePointerCapture(pointerId);
|
|
326
|
+
} catch {
|
|
327
|
+
// Capture may already be gone (pointercancel) — ignore.
|
|
328
|
+
}
|
|
329
|
+
const height = liveRowHeights.value[liveKey(cols, row)];
|
|
330
|
+
if (height !== undefined) void persistRowHeight(cols, row, height);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
function onResizeStart(index: number, event: PointerEvent): void {
|
|
334
|
+
const handle = event.currentTarget as HTMLElement;
|
|
335
|
+
// Pointer capture routes every subsequent move/up to the handle even
|
|
336
|
+
// when the cursor passes over the embedded view (incl. iframes), which
|
|
337
|
+
// is what made the drag drop events and feel unstable.
|
|
338
|
+
try {
|
|
339
|
+
handle.setPointerCapture(event.pointerId);
|
|
340
|
+
} catch {
|
|
341
|
+
// Pointer capture unsupported — fall back to plain window listeners.
|
|
342
|
+
}
|
|
343
|
+
resize = { cols: columns.value, row: rowOf(index), startY: event.clientY, startHeight: rowHeight(index), handle, pointerId: event.pointerId };
|
|
344
|
+
isResizing.value = true;
|
|
345
|
+
window.addEventListener("pointermove", onResizeMove);
|
|
346
|
+
window.addEventListener("pointerup", onResizeEnd);
|
|
347
|
+
window.addEventListener("pointercancel", onResizeEnd);
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
onBeforeUnmount(() => {
|
|
351
|
+
teardownResize();
|
|
352
|
+
teardownReorder();
|
|
353
|
+
columnQuery?.removeEventListener("change", syncColumns);
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
// First load: fold favorites into the stored layout once both lists are
|
|
357
|
+
// authoritatively loaded (so an empty pre-load list never prunes tiles).
|
|
358
|
+
onMounted(async () => {
|
|
359
|
+
columnQuery = window.matchMedia(SM_BREAKPOINT);
|
|
360
|
+
syncColumns();
|
|
361
|
+
columnQuery.addEventListener("change", syncColumns);
|
|
362
|
+
await Promise.all([loadShortcuts(), loadDashboard()]);
|
|
363
|
+
await reconcile(favoriteSlugs.value);
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
// Later pin/unpin while on the page re-reconciles. Non-immediate so it
|
|
367
|
+
// can't fire on the empty pre-load list.
|
|
368
|
+
watch(favoriteSlugs, (slugs) => void reconcile(slugs));
|
|
369
|
+
|
|
370
|
+
// Fetch each tile's schema as the layout settles.
|
|
371
|
+
watch(tiles, (list) => list.forEach((tile) => void loadSchema(tile.slug)), { immediate: true });
|
|
372
|
+
</script>
|
|
@@ -127,13 +127,24 @@
|
|
|
127
127
|
paths don't resolve under `srcdoc` (base URL is
|
|
128
128
|
`about:srcdoc`), but that's the historical behavior
|
|
129
129
|
for non-`artifacts/html/` HTML. -->
|
|
130
|
-
<
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
class="
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
130
|
+
<div v-else-if="isHtml && htmlPreviewUrl" class="relative w-full h-full">
|
|
131
|
+
<!-- Only `.html` is packable (server gates on `isHtmlPath`); a
|
|
132
|
+
`.htm` preview shows no button so the action can't 400. -->
|
|
133
|
+
<div v-if="canDownloadZip" class="absolute top-2 right-2 z-10 flex flex-col items-end gap-1">
|
|
134
|
+
<button
|
|
135
|
+
class="px-2 py-1 text-xs rounded border border-gray-300 bg-white/90 text-gray-600 hover:bg-gray-50 disabled:opacity-50 shadow-sm"
|
|
136
|
+
:title="t('fileContentRenderer.downloadZip')"
|
|
137
|
+
:disabled="packing"
|
|
138
|
+
data-testid="file-html-download"
|
|
139
|
+
@click="downloadHtmlZip"
|
|
140
|
+
>
|
|
141
|
+
<span class="material-icons text-sm align-middle">download</span>
|
|
142
|
+
{{ t("fileContentRenderer.download") }}
|
|
143
|
+
</button>
|
|
144
|
+
<span v-if="packFailed" class="text-xs text-red-600 bg-white/90 px-1 rounded" role="alert">{{ t("fileContentRenderer.downloadError") }}</span>
|
|
145
|
+
</div>
|
|
146
|
+
<iframe :src="htmlPreviewUrl" class="w-full h-full border-0" sandbox="allow-scripts" :title="t('fileContentRenderer.htmlPreview')" />
|
|
147
|
+
</div>
|
|
137
148
|
<iframe
|
|
138
149
|
v-else-if="isHtml"
|
|
139
150
|
:srcdoc="sandboxedHtml"
|
|
@@ -247,6 +258,7 @@ import type { JsonToken, JsonlLine } from "../utils/format/jsonSyntax";
|
|
|
247
258
|
import { formatScalarField, type MarkdownDocView } from "../composables/useMarkdownDoc";
|
|
248
259
|
import { rewriteMarkdownImageRefs } from "../utils/image/rewriteMarkdownImageRefs";
|
|
249
260
|
import { API_ROUTES } from "../config/apiRoutes";
|
|
261
|
+
import { useSharePack } from "../composables/useSharePack";
|
|
250
262
|
import { descriptorForPath, jsonEditableByPolicy } from "../config/systemFileDescriptors";
|
|
251
263
|
import { isMarpDocument } from "../utils/markdown/marpDetect";
|
|
252
264
|
import { buildPdfFilename } from "../utils/files/filename";
|
|
@@ -289,6 +301,19 @@ const emit = defineEmits<{
|
|
|
289
301
|
|
|
290
302
|
const systemDescriptor = computed(() => (props.selectedPath ? descriptorForPath(props.selectedPath) : null));
|
|
291
303
|
|
|
304
|
+
const { packing, packFailed, downloadZip, reset: resetSharePack } = useSharePack();
|
|
305
|
+
// `htmlPreviewUrl` also matches `.htm`, but the pack route only accepts
|
|
306
|
+
// canonical `.html`, so gate the button on `.html` to match server policy.
|
|
307
|
+
const canDownloadZip = computed(() => Boolean(props.isHtml && props.htmlPreviewUrl && props.selectedPath?.toLowerCase().endsWith(".html")));
|
|
308
|
+
function downloadHtmlZip() {
|
|
309
|
+
if (props.selectedPath) void downloadZip(props.selectedPath);
|
|
310
|
+
}
|
|
311
|
+
// Drop a stale error banner when the user navigates to another file.
|
|
312
|
+
watch(
|
|
313
|
+
() => props.selectedPath,
|
|
314
|
+
() => resetSharePack(),
|
|
315
|
+
);
|
|
316
|
+
|
|
292
317
|
const marpMode = computed(() => Boolean(props.mdFrontmatter && isMarpDocument(props.mdFrontmatter.meta)));
|
|
293
318
|
|
|
294
319
|
const marpBaseDir = computed(() => {
|
|
@@ -104,7 +104,7 @@ function createState(doc: string): EditorState {
|
|
|
104
104
|
labelCompartment.of(EditorView.contentAttributes.of({ "aria-label": props.editorLabel })),
|
|
105
105
|
EditorView.theme({
|
|
106
106
|
"&": { fontSize: "12px" },
|
|
107
|
-
".cm-content": { fontFamily:
|
|
107
|
+
".cm-content": { fontFamily: 'ui-monospace, SFMono-Regular, Menlo, Consolas, "MS Gothic", "BIZ UDGothic", monospace' },
|
|
108
108
|
"&.cm-focused": { outline: "2px solid rgb(96 165 250)" },
|
|
109
109
|
".cm-scroller": { overflow: "auto" },
|
|
110
110
|
}),
|