@nextclaw/ui 0.15.24 → 0.15.26
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/CHANGELOG.md +39 -0
- package/dist/assets/{abnfDiagram-VRR7QNED-DvTjgVMf.js → abnfDiagram-VRR7QNED-BcFJp_1X.js} +1 -1
- package/dist/assets/{aiden0z-pptx-renderer.es-C1nfBAXR.js → aiden0z-pptx-renderer.es-Dp2ezlQn.js} +1 -1
- package/dist/assets/{api-BxPNDp30.js → api-CK3Fro7z.js} +6 -6
- package/dist/assets/appearance-settings-page-zqwWO8Dy.js +1 -0
- package/dist/assets/{arc-BvhODBXv.js → arc-CkgyPMyW.js} +1 -1
- package/dist/assets/architecture-TIHT7OUA-DO2x53FK.js +1 -0
- package/dist/assets/{architectureDiagram-ZJ3FMSHR-BKO9RgNS.js → architectureDiagram-ZJ3FMSHR-CMd4cIjI.js} +1 -1
- package/dist/assets/{blockDiagram-677ZJIJ3-DHpDXy7D.js → blockDiagram-677ZJIJ3-Bz3tbhet.js} +1 -1
- package/dist/assets/{c4Diagram-LMCZKHZV-BG_iQnDr.js → c4Diagram-LMCZKHZV-D9hFNAGv.js} +1 -1
- package/dist/assets/channel-nBvMaVyP.js +1 -0
- package/dist/assets/{channels-list-page-DRaNWAxC.js → channels-list-page-CVCZBlCL.js} +2 -2
- package/dist/assets/chat-page-D4wehRTz.js +1 -0
- package/dist/assets/chunk-2Q5K7J3B-CJgqFDS4.js +1 -0
- package/dist/assets/{chunk-32BRIVSS-DR9M0LLx.js → chunk-32BRIVSS-7NrjEITq.js} +1 -1
- package/dist/assets/{chunk-52WLFC77-698iC3BB.js → chunk-52WLFC77-viIcqj9q.js} +1 -1
- package/dist/assets/{chunk-5FCAYU7R-DikTj9Ey.js → chunk-5FCAYU7R-Vsu4J-VM.js} +1 -1
- package/dist/assets/{chunk-5HE753X5-ko0o7_q0.js → chunk-5HE753X5-C-w6CQZK.js} +1 -1
- package/dist/assets/{chunk-5JV3BV7I-CEHufhgo.js → chunk-5JV3BV7I-CuKgJQ4W.js} +1 -1
- package/dist/assets/{chunk-5TONJI2A-CGgno1cu.js → chunk-5TONJI2A-CYeiWZKb.js} +1 -1
- package/dist/assets/{chunk-5VM5RSS4-DkiB4cUQ.js → chunk-5VM5RSS4-DRCbfwIn.js} +1 -1
- package/dist/assets/{chunk-7BUUIJ7U-DzptcytQ.js → chunk-7BUUIJ7U-BvydOcm3.js} +1 -1
- package/dist/assets/{chunk-BIQX33UG-DMRSQJvT.js → chunk-BIQX33UG-RlppOXKo.js} +1 -1
- package/dist/assets/{chunk-C7G6YPKG-Bw1BwwuY.js → chunk-C7G6YPKG-DAizzYwr.js} +1 -1
- package/dist/assets/{chunk-CQNSW5MT-C0z6eHBk.js → chunk-CQNSW5MT-5eChJHca.js} +1 -1
- package/dist/assets/{chunk-CYSBUYHQ-ByPyAouO.js → chunk-CYSBUYHQ-YHPr53fv.js} +1 -1
- package/dist/assets/{chunk-EMLP6XTP-CJlAXWZg.js → chunk-EMLP6XTP-CK4D7Hz5.js} +1 -1
- package/dist/assets/{chunk-EX3LRPZG-Dqq4zKJu.js → chunk-EX3LRPZG-BmlDFIRW.js} +1 -1
- package/dist/assets/{chunk-FWX5IMBZ-B0SNoCmi.js → chunk-FWX5IMBZ-D0ksHZZ9.js} +2 -2
- package/dist/assets/{chunk-HOUHSVGY-DG_GJih_.js → chunk-HOUHSVGY-DPkq8FCm.js} +1 -1
- package/dist/assets/{chunk-ICXQ74PX-Drxk_6bE.js → chunk-ICXQ74PX-FAatv5fC.js} +1 -1
- package/dist/assets/{chunk-JG7HCLWE-DdLFPpnd.js → chunk-JG7HCLWE-DLkIi4F7.js} +1 -1
- package/dist/assets/{chunk-JWPE2WC7-B8M4G76F.js → chunk-JWPE2WC7-DawOjNE3.js} +1 -1
- package/dist/assets/{chunk-MOJQB5TN-XP6FJHY_.js → chunk-MOJQB5TN-Di52TorW.js} +1 -1
- package/dist/assets/{chunk-MOZMSUNE-C_6x18Qw.js → chunk-MOZMSUNE-PfyK9w1-.js} +1 -1
- package/dist/assets/{chunk-OGEWGWER-N25BzCmG.js → chunk-OGEWGWER-C3jdH3Ty.js} +1 -1
- package/dist/assets/{chunk-OSBZ3O6U-ASDEm7y0.js → chunk-OSBZ3O6U-CecHo-f0.js} +1 -1
- package/dist/assets/{chunk-PUDLZKDR-CTlvAcn7.js → chunk-PUDLZKDR-f3IJh3sN.js} +1 -1
- package/dist/assets/{chunk-Q4XR5HBZ-Da9BWfB6.js → chunk-Q4XR5HBZ-DQFeUPxZ.js} +1 -1
- package/dist/assets/{chunk-QBLGF6JB-Bi6RpTJO.js → chunk-QBLGF6JB-BhUeG0y2.js} +1 -1
- package/dist/assets/{chunk-R7FJI6CG-DkMsSPvK.js → chunk-R7FJI6CG-C3XpZ-2u.js} +1 -1
- package/dist/assets/{chunk-RYQCIY6F-DZp0eU-j.js → chunk-RYQCIY6F-CAqa5klQ.js} +1 -1
- package/dist/assets/{chunk-U6XO7XAA-vfinDSrj.js → chunk-U6XO7XAA-D_WXezeD.js} +1 -1
- package/dist/assets/{chunk-V7JOEXUC-C9Nksccs.js → chunk-V7JOEXUC-CZ70oOtp.js} +1 -1
- package/dist/assets/chunk-VAUOI2AC-BGvh3yIx.js +1 -0
- package/dist/assets/{chunk-VR4S4FIN-Xv6zfwXs.js → chunk-VR4S4FIN-B7AHEYYk.js} +1 -1
- package/dist/assets/{chunk-WYO6CB5R-mOJNzbWl.js → chunk-WYO6CB5R-Dn9To_OI.js} +2 -2
- package/dist/assets/chunk-XXDRQBXY-DaBxG5gs.js +1 -0
- package/dist/assets/{chunk-YOTPTUD7-1-aKSu4o.js → chunk-YOTPTUD7-BYEgRBk3.js} +1 -1
- package/dist/assets/{chunk-ZGVPDNZ5-OJ8veMpK.js → chunk-ZGVPDNZ5-C6YtZDU0.js} +1 -1
- package/dist/assets/{chunk-ZIRB5QZD-SR_Nq_PP.js → chunk-ZIRB5QZD-DMuYtnHF.js} +1 -1
- package/dist/assets/classDiagram-OUVF2IWQ-P6N03G2R.js +1 -0
- package/dist/assets/classDiagram-v2-EOCWNBFH-B1_uLvMv.js +1 -0
- package/dist/assets/config-split-page-CceNFvlY.js +1 -0
- package/dist/assets/confirm-dialog-CWeNYWZY.js +5 -0
- package/dist/assets/{cose-bilkent-JH36ORCC-NjVgz2Bw.js → cose-bilkent-JH36ORCC-Bc2Ex5Y3.js} +1 -1
- package/dist/assets/cynefin-VYW2F7L2-B-H8Qp-n.js +1 -0
- package/dist/assets/{cynefinDiagram-TSTJHNR4-B_17LDqD.js → cynefinDiagram-TSTJHNR4-DVysQoGz.js} +1 -1
- package/dist/assets/{dagre-CMe101gk.js → dagre-CMhQ1cAG.js} +1 -1
- package/dist/assets/{dagre-VKFMJZFB-qo8nLb3B.js → dagre-VKFMJZFB-RYyWpPJZ.js} +1 -1
- package/dist/assets/desktop-update-config-Dirc3bpt.js +1 -0
- package/dist/assets/{diagram-FQU43EPY-Ba3XIklC.js → diagram-FQU43EPY-BD6KONdF.js} +1 -1
- package/dist/assets/{diagram-G47NLZAW-CG45TcUx.js → diagram-G47NLZAW-CGB2ptRR.js} +1 -1
- package/dist/assets/{diagram-NH7WQ7WH-2F2HrwV-.js → diagram-NH7WQ7WH-DnkOI6cU.js} +1 -1
- package/dist/assets/{diagram-OA4YK3LP-CXfWH9NZ.js → diagram-OA4YK3LP-DF82RZ7P.js} +1 -1
- package/dist/assets/{diagram-WEI45ONY-CH_4yepc.js → diagram-WEI45ONY-Be2v7Ck9.js} +1 -1
- package/dist/assets/{dist-B4Pf5X3Q.js → dist-AYxkFx2B.js} +1 -1
- package/dist/assets/{dist-DHEWkGWX.js → dist-D9xtGgdL.js} +1 -1
- package/dist/assets/doc-browser-4Sud6dWw.js +1 -0
- package/dist/assets/doc-browser-D4x_yH5F.js +1 -0
- package/dist/assets/doc-browser-context-Cb7Z0j90.js +1 -0
- package/dist/assets/{docx-preview-CoM4JK7_.js → docx-preview-BqwAjrj3.js} +1 -1
- package/dist/assets/{ebnfDiagram-CCIWWBDH-BwiG4Ea1.js → ebnfDiagram-CCIWWBDH-CP98OYk8.js} +1 -1
- package/dist/assets/{erDiagram-Q63AITRT-BML3hpnQ.js → erDiagram-Q63AITRT-ZLlx8xfs.js} +1 -1
- package/dist/assets/eventmodeling-45OFAUF4-YVtShyFA.js +1 -0
- package/dist/assets/flowDiagram-23GEKE2U-BeKXabVO.js +1 -0
- package/dist/assets/form-actions-B2q4jgjE.js +1 -0
- package/dist/assets/{ganttDiagram-NO4QXBWP-80enfWbD.js → ganttDiagram-NO4QXBWP-DXCDjHR2.js} +1 -1
- package/dist/assets/gitGraph-TEB2WS4Q-jTSiBj-p.js +1 -0
- package/dist/assets/{gitGraphDiagram-IHSO6WYX-CVLqXr-b.js → gitGraphDiagram-IHSO6WYX-l2HWrTJp.js} +1 -1
- package/dist/assets/index-CdyTsSBu.css +1 -0
- package/dist/assets/index-Das_bw2h.js +107 -0
- package/dist/assets/info-DKCQHKI2-BIporNWb.js +1 -0
- package/dist/assets/{infoDiagram-FWYZ7A6U-Dyvs2t0i.js → infoDiagram-FWYZ7A6U-D-DjX-bn.js} +1 -1
- package/dist/assets/{ishikawaDiagram-FXEZZL3T-sSnxDKfh.js → ishikawaDiagram-FXEZZL3T-y_Xy128F.js} +1 -1
- package/dist/assets/{journeyDiagram-5HDEW3XC-D22V71Mf.js → journeyDiagram-5HDEW3XC-DJn5xYcQ.js} +1 -1
- package/dist/assets/{kanban-definition-HUTT4EX6-D3ds0p-g.js → kanban-definition-HUTT4EX6-DJVIOR21.js} +1 -1
- package/dist/assets/{line-CbyFs065.js → line-Cos7PIOj.js} +1 -1
- package/dist/assets/{linear-4DKtnZBS.js → linear-BtDLqtzF.js} +1 -1
- package/dist/assets/loader-circle-CpmBdaon.js +1 -0
- package/dist/assets/{map-CcZzEoz6.js → map-BKnQyvd0.js} +1 -1
- package/dist/assets/mcp-marketplace-page-CdOY3Sop.js +1 -0
- package/dist/assets/mcp-marketplace-page-DvjrfqEt.js +9 -0
- package/dist/assets/{mermaid-parser.core-DPmMOjXD.js → mermaid-parser.core-XUNZhr2W.js} +2 -2
- package/dist/assets/{mermaid.core-BSf-ddRa.js → mermaid.core-ySrvolIe.js} +3 -3
- package/dist/assets/{mindmap-definition-LN4V7U3C-BUTwWPyx.js → mindmap-definition-LN4V7U3C-BypL9tei.js} +1 -1
- package/dist/assets/model-config-page-DfOLyQqR.js +1 -0
- package/dist/assets/{navigation-link-CAFOcXNg.js → navigation-link-MiiyoIjH.js} +2 -2
- package/dist/assets/{ordinal-BgLYtOZE.js → ordinal-CxxVWxYU.js} +1 -1
- package/dist/assets/packet-7NZHBO7P-CuFMADV0.js +1 -0
- package/dist/assets/{pegDiagram-2B236MQR-BEu_ZBjc.js → pegDiagram-2B236MQR-D-U2KBGv.js} +1 -1
- package/dist/assets/pie-RZYD4A2V-XUeIXhM9.js +1 -0
- package/dist/assets/{pieDiagram-ENE6RG2P-CAwAwuBd.js → pieDiagram-ENE6RG2P-CBi3GusJ.js} +1 -1
- package/dist/assets/plus-C8FkjpSZ.js +1 -0
- package/dist/assets/provider-scoped-model-input-DXzjr5l0.js +1 -0
- package/dist/assets/providers-config-page-CaO8WfZH.js +1 -0
- package/dist/assets/{quadrantDiagram-ABIIQ3AL-CwE6F-_2.js → quadrantDiagram-ABIIQ3AL-H0ZuvnZL.js} +1 -1
- package/dist/assets/radar-I7S5WNFK-D-NNGh1k.js +1 -0
- package/dist/assets/railroad-3IZDKUUU-C67_uDaM.js +1 -0
- package/dist/assets/railroad-abnf-AHOZXSZD-BZ8Yflnd.js +1 -0
- package/dist/assets/railroad-ebnf-EBAXGLYW-B7zO7RoK.js +1 -0
- package/dist/assets/railroad-peg-LSFZ7HO6-R63PTgLz.js +1 -0
- package/dist/assets/{railroadDiagram-RFXS5EU6-xgLekU3B.js → railroadDiagram-RFXS5EU6-D7Y2EqSd.js} +1 -1
- package/dist/assets/react-C8XFFvuz.js +8 -0
- package/dist/assets/{refresh-cw-CWDp_JnN.js → refresh-cw-a2VVeeAF.js} +1 -1
- package/dist/assets/remote-BJHXa4mB.js +1 -0
- package/dist/assets/{requirementDiagram-TGXJPOKE-CyVUzEX5.js → requirementDiagram-TGXJPOKE-BiXNX1NN.js} +1 -1
- package/dist/assets/rotate-cw-DJ7UF3F2.js +1 -0
- package/dist/assets/runtime-config-page-BuA4A02z.js +1 -0
- package/dist/assets/{sankeyDiagram-HTMAVEWB-JK6bFmYB.js → sankeyDiagram-HTMAVEWB-qzDeLztW.js} +1 -1
- package/dist/assets/{save-Cnr71JON.js → save-CGm23Yww.js} +1 -1
- package/dist/assets/{search-CCX11X2m.js → search-CH2t--sH.js} +1 -1
- package/dist/assets/search-config-page-BAmYqoAz.js +1 -0
- package/dist/assets/secrets-config-page-BlPOuql_.js +3 -0
- package/dist/assets/security-config-CbDiY2xr.js +1 -0
- package/dist/assets/{select-DvwBu99K.js → select-DnxxDpcV.js} +2 -2
- package/dist/assets/{sequenceDiagram-DBY2YBRQ-BR5IokXV.js → sequenceDiagram-DBY2YBRQ-BtRbPQtL.js} +1 -1
- package/dist/assets/{settings-2-Dmv6zMne.js → settings-2-agnMYc8a.js} +1 -1
- package/dist/assets/{sizeCapture-X5ZJPWSS-SxG_YnMA.js → sizeCapture-X5ZJPWSS-CMYvM3SL.js} +1 -1
- package/dist/assets/skeleton-XpqeWL62.js +1 -0
- package/dist/assets/{src-vgz8E_-O.js → src-Bw-mzbQb.js} +1 -1
- package/dist/assets/{stateDiagram-2N3HPSRC-DD4OoYhn.js → stateDiagram-2N3HPSRC-BJ-mFeOz.js} +1 -1
- package/dist/assets/stateDiagram-v2-6OUMAXLB-BE-HqxOK.js +1 -0
- package/dist/assets/{swimlanes-5IMT3BWC-BWkFeyCT.js → swimlanes-5IMT3BWC-BsucVOO2.js} +2 -2
- package/dist/assets/swimlanesDiagram-G3AALYLV-Dr1H9LJH.js +8 -0
- package/dist/assets/{tag-chip-gMPvx7n_.js → tag-chip-CPOc8b2N.js} +1 -1
- package/dist/assets/{timeline-definition-FHXFAJF6-VTRPy6KA.js → timeline-definition-FHXFAJF6-Bicewlzo.js} +1 -1
- package/dist/assets/treeView-QDETBFTQ-npJ5dbqS.js +1 -0
- package/dist/assets/treemap-6X3UGDF4-pYrBx6oa.js +1 -0
- package/dist/assets/{vennDiagram-L72KCM5P-Bvk42Pek.js → vennDiagram-L72KCM5P-BxOOnO9x.js} +1 -1
- package/dist/assets/wardley-OPB4EBWU-CmSRKLR8.js +1 -0
- package/dist/assets/{wardleyDiagram-EHGQE667-DOFjx2s3.js → wardleyDiagram-EHGQE667-Cl7NltMv.js} +1 -1
- package/dist/assets/x-odTHAtLZ.js +1 -0
- package/dist/assets/{xychartDiagram-FW5EYKEG-DY5yRTUc.js → xychartDiagram-FW5EYKEG-DiMIxQ7N.js} +1 -1
- package/dist/index.html +21 -22
- package/package.json +8 -8
- package/src/features/apps/components/__tests__/app-packages-panel.test.tsx +234 -0
- package/src/features/apps/components/app-marketplace-dialog.tsx +262 -0
- package/src/features/apps/components/app-package-card.tsx +268 -0
- package/src/features/apps/components/app-packages-panel.tsx +301 -0
- package/src/features/apps/components/apps-panel.tsx +11 -4
- package/src/features/apps/hooks/use-app-marketplace.ts +95 -0
- package/src/features/apps/hooks/use-app-packages.ts +59 -0
- package/src/features/chat/components/chat-sidebar-list-mode-switch.tsx +27 -4
- package/src/features/chat/components/layout/__tests__/chat-sidebar-session-area.test.tsx +36 -5
- package/src/features/chat/components/layout/chat-sidebar-desktop-layout.tsx +12 -13
- package/src/features/chat/features/conversation/components/__tests__/session-conversation-area.test.tsx +79 -2
- package/src/features/chat/features/conversation/components/__tests__/session-conversation-input.streaming.test.tsx +4 -4
- package/src/features/chat/features/conversation/components/session-conversation-area.tsx +26 -5
- package/src/features/chat/features/conversation/components/session-conversation-input.tsx +3 -0
- package/src/features/chat/features/conversation/hooks/use-system-object-reference-select.ts +27 -0
- package/src/features/chat/features/conversation/utils/session-message-composer.utils.ts +5 -0
- package/src/features/chat/features/input/hooks/__tests__/use-chat-input-surface-state.test.tsx +102 -0
- package/src/features/chat/features/input/hooks/use-chat-input-surface-state.ts +71 -120
- package/src/features/chat/features/input/input-surface-plugins/__tests__/context-reference-plugin.test.ts +265 -18
- package/src/features/chat/features/input/input-surface-plugins/chat-input-product-plugin-adapters.types.ts +7 -2
- package/src/features/chat/features/input/input-surface-plugins/chat-input-product-plugin-adapters.utils.ts +124 -0
- package/src/features/chat/features/input/input-surface-plugins/context-reference-items.utils.ts +99 -15
- package/src/features/chat/features/input/input-surface-plugins/context-reference-plugin.utils.ts +143 -139
- package/src/features/chat/features/input/input-surface-plugins/panel-app-input-surface-items.utils.ts +116 -0
- package/src/features/chat/features/input/input-surface-plugins/slash-command-plugin.utils.ts +1 -1
- package/src/features/chat/features/input/utils/__tests__/chat-inline-token.utils.test.ts +53 -1
- package/src/features/chat/features/input/utils/chat-composer-token-protocol.utils.ts +5 -0
- package/src/features/chat/features/input/utils/chat-inline-token-composer.utils.ts +134 -0
- package/src/features/chat/features/input/utils/chat-inline-token.utils.ts +30 -101
- package/src/features/chat/features/input/utils/chat-system-object-reference.utils.test.ts +44 -0
- package/src/features/chat/features/input/utils/chat-system-object-reference.utils.ts +46 -0
- package/src/features/chat/features/ncp/hooks/__tests__/use-hydrated-ncp-agent.test.tsx +40 -3
- package/src/features/chat/features/workspace/components/__tests__/chat-session-workspace-file-preview.selection.test.tsx +66 -0
- package/src/features/chat/features/workspace/components/__tests__/chat-session-workspace-file-preview.test.tsx +13 -1
- package/src/features/chat/features/workspace/components/__tests__/chat-session-workspace-panel-content.test.tsx +653 -90
- package/src/features/chat/features/workspace/components/__tests__/chat-session-workspace-text-selection.test.tsx +12 -8
- package/src/features/chat/features/workspace/components/chat-session-workspace-directory-browser.tsx +401 -288
- package/src/features/chat/features/workspace/components/chat-session-workspace-file-breadcrumbs.tsx +54 -35
- package/src/features/chat/features/workspace/components/chat-session-workspace-file-preview.tsx +79 -148
- package/src/features/chat/features/workspace/components/chat-session-workspace-panel-content.tsx +156 -94
- package/src/features/chat/features/workspace/components/project-files/__tests__/workspace-directory-entry-menu.test.tsx +50 -0
- package/src/features/chat/features/workspace/components/project-files/workspace-directory-entry-menu.tsx +209 -0
- package/src/features/chat/features/workspace/components/project-files/workspace-directory-tree.tsx +459 -0
- package/src/features/chat/features/workspace/components/project-files/workspace-new-folder-tree-item.tsx +112 -0
- package/src/features/chat/features/workspace/components/project-files/workspace-project-files-toolbar.tsx +64 -0
- package/src/features/chat/features/workspace/hooks/use-workspace-explorer-layout.ts +85 -0
- package/src/features/chat/features/workspace/hooks/use-workspace-file-actions.ts +357 -0
- package/src/features/chat/features/workspace/utils/chat-workspace-file-path-mutation.utils.ts +58 -0
- package/src/features/chat/features/workspace/utils/chat-workspace-file-tab.utils.ts +23 -22
- package/src/features/chat/features/workspace/utils/chat-workspace-panel-layout.utils.ts +13 -4
- package/src/features/chat/index.ts +1 -1
- package/src/features/chat/managers/__tests__/chat-composer-intent.manager.test.ts +86 -60
- package/src/features/chat/managers/__tests__/chat-thread.manager.test.ts +103 -99
- package/src/features/chat/managers/chat-composer-intent.manager.ts +37 -33
- package/src/features/chat/managers/chat-draft-intent.manager.ts +18 -2
- package/src/features/chat/managers/chat-thread.manager.ts +132 -128
- package/src/features/chat/stores/__tests__/chat-thread.store.test.ts +26 -40
- package/src/features/chat/stores/chat-thread.store.ts +53 -74
- package/src/features/inbox/components/inbox-reader-dialog.test.tsx +38 -5
- package/src/features/inbox/components/inbox-reader-dialog.tsx +5 -3
- package/src/features/inbox/managers/inbox.manager.test.ts +28 -3
- package/src/features/inbox/managers/inbox.manager.ts +14 -4
- package/src/features/inbox/pages/inbox-page.test.ts +121 -2
- package/src/features/inbox/pages/inbox-page.tsx +5 -3
- package/src/features/marketplace/components/__tests__/marketplace-page.test.tsx +85 -1
- package/src/features/marketplace/hooks/__tests__/use-marketplace.test.tsx +44 -1
- package/src/features/marketplace/hooks/use-marketplace-item-actions.ts +23 -2
- package/src/features/marketplace/hooks/use-marketplace.ts +10 -0
- package/src/features/marketplace/index.ts +4 -0
- package/src/features/panel-apps/index.ts +5 -1
- package/src/features/panel-apps/utils/__tests__/panel-app-doc-browser.utils.test.ts +13 -6
- package/src/features/panel-apps/utils/panel-app-doc-browser.utils.tsx +6 -3
- package/src/features/right-panel-resources/index.ts +1 -0
- package/src/features/right-panel-resources/utils/right-panel-resource-uri.utils.ts +7 -4
- package/src/features/system-status/components/__tests__/desktop-update-config.test.tsx +20 -1
- package/src/features/system-status/components/desktop-update-config.tsx +14 -13
- package/src/features/system-status/managers/__tests__/runtime-update.manager.test.ts +99 -0
- package/src/features/system-status/managers/runtime-update.manager.ts +22 -0
- package/src/features/system-status/stores/runtime-update.store.ts +1 -0
- package/src/shared/components/resizable-right-panel/__tests__/resizable-right-panel.test.tsx +35 -38
- package/src/shared/components/resizable-right-panel/resizable-right-panel.tsx +25 -19
- package/src/shared/components/ui/actions/__tests__/icon-action-button.test.tsx +15 -0
- package/src/shared/components/ui/actions/icon-action-button.tsx +4 -2
- package/src/shared/components/ui/context-menu/__tests__/context-menu.test.tsx +60 -34
- package/src/shared/components/ui/context-menu/context-menu.tsx +95 -68
- package/src/shared/hooks/use-system-object-references.ts +22 -0
- package/src/shared/lib/api/managers/client.manager.ts +31 -17
- package/src/shared/lib/api/server-path/server-path.types.ts +66 -0
- package/src/shared/lib/api/types.ts +155 -116
- package/src/shared/lib/api/utils/server-path.utils.ts +35 -11
- package/src/shared/lib/i18n/locales/en-US/chat.json +58 -9
- package/src/shared/lib/i18n/locales/en-US/desktop-update.json +7 -4
- package/src/shared/lib/i18n/locales/en-US/doc-browser.json +40 -0
- package/src/shared/lib/i18n/locales/en-US/marketplace.json +3 -0
- package/src/shared/lib/i18n/locales/zh-CN/chat.json +60 -11
- package/src/shared/lib/i18n/locales/zh-CN/desktop-update.json +7 -4
- package/src/shared/lib/i18n/locales/zh-CN/doc-browser.json +40 -0
- package/src/shared/lib/i18n/locales/zh-CN/marketplace.json +3 -0
- package/dist/assets/appearance-settings-page-DpVJjvkB.js +0 -1
- package/dist/assets/architecture-TIHT7OUA-Bjp9jf0h.js +0 -1
- package/dist/assets/channel-5T3pdR-s.js +0 -1
- package/dist/assets/chat-page-BuFDmG5_.js +0 -1
- package/dist/assets/chunk-2Q5K7J3B-DTZ3tGfE.js +0 -1
- package/dist/assets/chunk-VAUOI2AC-HLGIHQ6k.js +0 -1
- package/dist/assets/chunk-XXDRQBXY-Dl0m6Ni-.js +0 -1
- package/dist/assets/classDiagram-OUVF2IWQ-DcDnzGNX.js +0 -1
- package/dist/assets/classDiagram-v2-EOCWNBFH-BKXKMzKM.js +0 -1
- package/dist/assets/config-split-page-BVWVDbvG.js +0 -1
- package/dist/assets/confirm-dialog-mA2iVzxh.js +0 -5
- package/dist/assets/cynefin-VYW2F7L2-BWg7U2UD.js +0 -1
- package/dist/assets/desktop-update-config-DyQb1LXr.js +0 -1
- package/dist/assets/doc-browser-CtO_HtRy.js +0 -1
- package/dist/assets/doc-browser-context-CuGI6PQc.js +0 -1
- package/dist/assets/doc-browser-eMUagyBV.js +0 -1
- package/dist/assets/eventmodeling-45OFAUF4-SB-J4tpq.js +0 -1
- package/dist/assets/flowDiagram-23GEKE2U-BQ_u-eLM.js +0 -1
- package/dist/assets/form-actions-BOfIyG3O.js +0 -1
- package/dist/assets/gitGraph-TEB2WS4Q-DPxNwsFd.js +0 -1
- package/dist/assets/index-BjBdWHV1.js +0 -107
- package/dist/assets/index-DmpmjAGQ.css +0 -1
- package/dist/assets/info-DKCQHKI2-DNlNGH90.js +0 -1
- package/dist/assets/loader-circle-Df8wF2Pr.js +0 -1
- package/dist/assets/mcp-marketplace-page-DI49IKxp.js +0 -1
- package/dist/assets/mcp-marketplace-page-ofDqbOIF.js +0 -9
- package/dist/assets/model-config-page-D73Tu3jW.js +0 -1
- package/dist/assets/packet-7NZHBO7P-BpeiqkBZ.js +0 -1
- package/dist/assets/pie-RZYD4A2V-Cy1yxsPd.js +0 -1
- package/dist/assets/plus-DVU5RLCS.js +0 -1
- package/dist/assets/provider-scoped-model-input-BaJJJYMX.js +0 -1
- package/dist/assets/providers-config-page-BL6IRVBm.js +0 -1
- package/dist/assets/radar-I7S5WNFK-CEcPKCj8.js +0 -1
- package/dist/assets/railroad-3IZDKUUU-CW38eNDs.js +0 -1
- package/dist/assets/railroad-abnf-AHOZXSZD-ByrylcXf.js +0 -1
- package/dist/assets/railroad-ebnf-EBAXGLYW-CNB6GwP-.js +0 -1
- package/dist/assets/railroad-peg-LSFZ7HO6-x6hnqlBG.js +0 -1
- package/dist/assets/react-BC_orpAj.js +0 -8
- package/dist/assets/remote-T24O58lH.js +0 -1
- package/dist/assets/rotate-cw-BqhwSV5p.js +0 -1
- package/dist/assets/runtime-config-page-CXUx017t.js +0 -1
- package/dist/assets/search-config-page-BB6d5gT7.js +0 -1
- package/dist/assets/secrets-config-page-DYsDeT9K.js +0 -3
- package/dist/assets/security-config-CVPgqL7G.js +0 -1
- package/dist/assets/skeleton-C5tWata3.js +0 -1
- package/dist/assets/stateDiagram-v2-6OUMAXLB-ZC6dFORv.js +0 -1
- package/dist/assets/swimlanesDiagram-G3AALYLV-CRkh51Im.js +0 -8
- package/dist/assets/treeView-QDETBFTQ-Bw0DSCH7.js +0 -1
- package/dist/assets/treemap-6X3UGDF4-DFWBFcfh.js +0 -1
- package/dist/assets/use-confirm-dialog-D_3-QpZU.js +0 -1
- package/dist/assets/wardley-OPB4EBWU-CoWturk0.js +0 -1
- package/dist/assets/x-BEWSqeux.js +0 -1
- /package/dist/assets/{array-NADKd9aU.js → array-BQVzeGRt.js} +0 -0
- /package/dist/assets/{chunk-KEIR6QF5-BuyuzJJL.js → chunk-KEIR6QF5-f9psyCXK.js} +0 -0
- /package/dist/assets/{chunk-Y2CYZVJY-D_B2ZRNt.js → chunk-Y2CYZVJY-D1mAU_Po.js} +0 -0
- /package/dist/assets/{config-hints-BcfVUMij.js → config-hints-nV5HPOvq.js} +0 -0
- /package/dist/assets/{cytoscape.esm-BANTlgza.js → cytoscape.esm-0uDtdD0l.js} +0 -0
- /package/dist/assets/{defaultLocale-C4_L0aEs.js → defaultLocale-DSWXGgt0.js} +0 -0
- /package/dist/assets/{dist-DAPEIJbz.js → dist--ta5rX12.js} +0 -0
- /package/dist/assets/{graphlib-ChqXg0dI.js → graphlib-CQ6QV4yP.js} +0 -0
- /package/dist/assets/{host-capabilities-D9C4CBK5.js → host-capabilities-Tk038i04.js} +0 -0
- /package/dist/assets/{init-D8wZg9xq.js → init-Y6h9jGtZ.js} +0 -0
- /package/dist/assets/{jszip.min-CkbBfwAQ.js → jszip.min-8hyJpgpx.js} +0 -0
- /package/dist/assets/{katex-TZJFa0J_.js → katex-ARhY9PS1.js} +0 -0
- /package/dist/assets/{middleware-B3k-TSmZ.js → middleware-DXsbf9xI.js} +0 -0
- /package/dist/assets/{path-DodfxcX7.js → path-10BjdyLh.js} +0 -0
- /package/dist/assets/{rough.esm-CUaa1Rbe.js → rough.esm-DxgC_14r.js} +0 -0
- /package/dist/assets/{xlsx-CxByi7bR.js → xlsx-DrDuDNAa.js} +0 -0
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { fireEvent, render, screen } from "@testing-library/react";
|
|
1
|
+
import { fireEvent, render, screen, waitFor } from "@testing-library/react";
|
|
2
2
|
import { MarketplacePage } from "@/features/marketplace";
|
|
3
|
+
import { NextClawClientError } from "@nextclaw/client-sdk";
|
|
3
4
|
import type {
|
|
4
5
|
MarketplaceInstalledView,
|
|
5
6
|
MarketplaceItemSummary,
|
|
@@ -82,6 +83,8 @@ vi.mock("@/shared/lib/host-capabilities", () => ({
|
|
|
82
83
|
}));
|
|
83
84
|
|
|
84
85
|
vi.mock("@/features/marketplace/hooks/use-marketplace", () => ({
|
|
86
|
+
isMarketplaceSkillLocalChangesError: (error: { code?: string }) =>
|
|
87
|
+
error?.code === "MARKETPLACE_SKILL_LOCAL_CHANGES",
|
|
85
88
|
useMarketplaceItems: () => mocks.itemsQuery,
|
|
86
89
|
useMarketplaceRecentItems: () => mocks.recentItemsQuery,
|
|
87
90
|
useMarketplaceSkillScenes: () => ({
|
|
@@ -160,6 +163,43 @@ function createInstalledQuery(
|
|
|
160
163
|
};
|
|
161
164
|
}
|
|
162
165
|
|
|
166
|
+
function prepareInstalledSkillUpdate() {
|
|
167
|
+
const item = createMarketplaceItem();
|
|
168
|
+
mocks.itemsQuery = createItemsQuery({
|
|
169
|
+
data: {
|
|
170
|
+
total: 1,
|
|
171
|
+
page: 1,
|
|
172
|
+
pageSize: 20,
|
|
173
|
+
totalPages: 1,
|
|
174
|
+
sort: "relevance",
|
|
175
|
+
items: [item],
|
|
176
|
+
} satisfies MarketplaceListView,
|
|
177
|
+
});
|
|
178
|
+
mocks.installedQuery = createInstalledQuery({
|
|
179
|
+
data: {
|
|
180
|
+
type: "skill",
|
|
181
|
+
total: 1,
|
|
182
|
+
specs: [item.slug],
|
|
183
|
+
records: [{
|
|
184
|
+
type: "skill",
|
|
185
|
+
id: item.slug,
|
|
186
|
+
spec: item.slug,
|
|
187
|
+
source: "workspace",
|
|
188
|
+
origin: "marketplace",
|
|
189
|
+
catalogSlug: item.slug,
|
|
190
|
+
enabled: true,
|
|
191
|
+
}],
|
|
192
|
+
} satisfies MarketplaceInstalledView,
|
|
193
|
+
});
|
|
194
|
+
mocks.manageMutation.mutateAsync.mockRejectedValueOnce(
|
|
195
|
+
new NextClawClientError({
|
|
196
|
+
message: "Local skill files changed since install: web-search",
|
|
197
|
+
status: 409,
|
|
198
|
+
code: "MARKETPLACE_SKILL_LOCAL_CHANGES",
|
|
199
|
+
}),
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
|
|
163
203
|
describe("MarketplacePage", () => {
|
|
164
204
|
beforeEach(() => {
|
|
165
205
|
mocks.navigate.mockReset();
|
|
@@ -364,4 +404,48 @@ describe("MarketplacePage", () => {
|
|
|
364
404
|
|
|
365
405
|
expect(mocks.openExternalUrl).toHaveBeenCalledWith("https://skillhub.cn/");
|
|
366
406
|
});
|
|
407
|
+
|
|
408
|
+
it("asks before overwriting local skill changes and retries the update with force", async () => {
|
|
409
|
+
prepareInstalledSkillUpdate();
|
|
410
|
+
mocks.manageMutation.mutateAsync.mockResolvedValueOnce({});
|
|
411
|
+
mocks.confirm.mockResolvedValueOnce(true);
|
|
412
|
+
|
|
413
|
+
render(<MarketplacePage forcedType="skills" />);
|
|
414
|
+
fireEvent.click(screen.getByRole("button", { name: "Update" }));
|
|
415
|
+
|
|
416
|
+
await waitFor(() => expect(mocks.confirm).toHaveBeenCalledWith({
|
|
417
|
+
title: "Overwrite local changes and update?",
|
|
418
|
+
description: "This skill was changed after installation. Continuing replaces local changes in its skill directory with the latest marketplace version. Canceling leaves the current files unchanged.",
|
|
419
|
+
confirmLabel: "Overwrite and Update",
|
|
420
|
+
variant: "destructive",
|
|
421
|
+
}));
|
|
422
|
+
await waitFor(() => expect(mocks.manageMutation.mutateAsync).toHaveBeenCalledTimes(2));
|
|
423
|
+
expect(mocks.manageMutation.mutateAsync).toHaveBeenNthCalledWith(1, {
|
|
424
|
+
type: "skill",
|
|
425
|
+
action: "update",
|
|
426
|
+
id: "web-search",
|
|
427
|
+
spec: "web-search",
|
|
428
|
+
});
|
|
429
|
+
expect(mocks.manageMutation.mutateAsync).toHaveBeenNthCalledWith(2, {
|
|
430
|
+
type: "skill",
|
|
431
|
+
action: "update",
|
|
432
|
+
id: "web-search",
|
|
433
|
+
spec: "web-search",
|
|
434
|
+
force: true,
|
|
435
|
+
});
|
|
436
|
+
});
|
|
437
|
+
|
|
438
|
+
it("keeps local skill changes untouched when overwrite is canceled", async () => {
|
|
439
|
+
prepareInstalledSkillUpdate();
|
|
440
|
+
mocks.confirm.mockResolvedValueOnce(false);
|
|
441
|
+
|
|
442
|
+
render(<MarketplacePage forcedType="skills" />);
|
|
443
|
+
fireEvent.click(screen.getByRole("button", { name: "Update" }));
|
|
444
|
+
|
|
445
|
+
await waitFor(() => expect(mocks.confirm).toHaveBeenCalledTimes(1));
|
|
446
|
+
expect(mocks.manageMutation.mutateAsync).toHaveBeenCalledTimes(1);
|
|
447
|
+
expect(mocks.manageMutation.mutateAsync).not.toHaveBeenCalledWith(
|
|
448
|
+
expect.objectContaining({ force: true }),
|
|
449
|
+
);
|
|
450
|
+
});
|
|
367
451
|
});
|
|
@@ -1,14 +1,25 @@
|
|
|
1
1
|
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
|
2
|
-
import { renderHook, waitFor } from "@testing-library/react";
|
|
2
|
+
import { act, renderHook, waitFor } from "@testing-library/react";
|
|
3
3
|
import type { ReactNode } from "react";
|
|
4
4
|
import {
|
|
5
|
+
useManageMarketplaceItem,
|
|
5
6
|
useMarketplaceItems,
|
|
6
7
|
useMarketplaceRecentItems,
|
|
7
8
|
} from "@/features/marketplace/hooks/use-marketplace";
|
|
9
|
+
import { NextClawClientError } from "@nextclaw/client-sdk";
|
|
8
10
|
import type { MarketplaceListView } from "@/shared/lib/api";
|
|
9
11
|
|
|
10
12
|
const mocks = vi.hoisted(() => ({
|
|
11
13
|
fetchMarketplaceItems: vi.fn(),
|
|
14
|
+
manageMarketplaceItem: vi.fn(),
|
|
15
|
+
toastError: vi.fn(),
|
|
16
|
+
}));
|
|
17
|
+
|
|
18
|
+
vi.mock("sonner", () => ({
|
|
19
|
+
toast: {
|
|
20
|
+
error: mocks.toastError,
|
|
21
|
+
success: vi.fn(),
|
|
22
|
+
},
|
|
12
23
|
}));
|
|
13
24
|
|
|
14
25
|
vi.mock("@/shared/lib/api", async () => {
|
|
@@ -16,6 +27,7 @@ vi.mock("@/shared/lib/api", async () => {
|
|
|
16
27
|
return {
|
|
17
28
|
...(actual as object),
|
|
18
29
|
fetchMarketplaceItems: mocks.fetchMarketplaceItems,
|
|
30
|
+
manageMarketplaceItem: mocks.manageMarketplaceItem,
|
|
19
31
|
};
|
|
20
32
|
});
|
|
21
33
|
|
|
@@ -132,3 +144,34 @@ describe("useMarketplaceItems", () => {
|
|
|
132
144
|
});
|
|
133
145
|
});
|
|
134
146
|
});
|
|
147
|
+
|
|
148
|
+
describe("useManageMarketplaceItem", () => {
|
|
149
|
+
beforeEach(() => {
|
|
150
|
+
mocks.manageMarketplaceItem.mockReset();
|
|
151
|
+
mocks.toastError.mockReset();
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
it("leaves local-change conflicts for the explicit overwrite confirmation", async () => {
|
|
155
|
+
const conflict = new NextClawClientError({
|
|
156
|
+
message: "Local skill files changed since install: web-search",
|
|
157
|
+
status: 409,
|
|
158
|
+
code: "MARKETPLACE_SKILL_LOCAL_CHANGES",
|
|
159
|
+
});
|
|
160
|
+
mocks.manageMarketplaceItem.mockRejectedValueOnce(conflict);
|
|
161
|
+
|
|
162
|
+
const { result } = renderHook(() => useManageMarketplaceItem(), {
|
|
163
|
+
wrapper: createWrapper(),
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
await act(async () => {
|
|
167
|
+
await expect(result.current.mutateAsync({
|
|
168
|
+
type: "skill",
|
|
169
|
+
action: "update",
|
|
170
|
+
id: "web-search",
|
|
171
|
+
spec: "web-search",
|
|
172
|
+
})).rejects.toBe(conflict);
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
expect(mocks.toastError).not.toHaveBeenCalled();
|
|
176
|
+
});
|
|
177
|
+
});
|
|
@@ -4,6 +4,7 @@ import type {
|
|
|
4
4
|
MarketplaceManageAction,
|
|
5
5
|
} from "@/shared/lib/api";
|
|
6
6
|
import {
|
|
7
|
+
isMarketplaceSkillLocalChangesError,
|
|
7
8
|
useInstallMarketplaceItem,
|
|
8
9
|
useManageMarketplaceItem,
|
|
9
10
|
} from "@/features/marketplace/hooks/use-marketplace";
|
|
@@ -76,12 +77,32 @@ export function useMarketplaceItemActions() {
|
|
|
76
77
|
setManagingTargets((previous) => new Map(previous).set(targetId, action));
|
|
77
78
|
|
|
78
79
|
try {
|
|
79
|
-
|
|
80
|
+
const request = {
|
|
80
81
|
type: record.type,
|
|
81
82
|
action,
|
|
82
83
|
id: targetId,
|
|
83
84
|
spec: record.spec,
|
|
84
|
-
}
|
|
85
|
+
} as const;
|
|
86
|
+
try {
|
|
87
|
+
await manageMutation.mutateAsync(request);
|
|
88
|
+
} catch (error) {
|
|
89
|
+
if (action !== "update" || !isMarketplaceSkillLocalChangesError(error)) {
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
const confirmed = await confirm({
|
|
93
|
+
title: t("marketplaceUpdateConflictTitle"),
|
|
94
|
+
description: t("marketplaceUpdateConflictDescription"),
|
|
95
|
+
confirmLabel: t("marketplaceUpdateConflictConfirm"),
|
|
96
|
+
variant: "destructive",
|
|
97
|
+
});
|
|
98
|
+
if (!confirmed) {
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
await manageMutation.mutateAsync({
|
|
102
|
+
...request,
|
|
103
|
+
force: true,
|
|
104
|
+
});
|
|
105
|
+
}
|
|
85
106
|
} finally {
|
|
86
107
|
setManagingTargets((previous) => {
|
|
87
108
|
const next = new Map(previous);
|
|
@@ -24,8 +24,15 @@ import type {
|
|
|
24
24
|
} from '@/shared/lib/api';
|
|
25
25
|
import { collapseMarketplaceListPages } from '@/features/marketplace/utils/marketplace-list-pages.utils';
|
|
26
26
|
import { useMemo } from 'react';
|
|
27
|
+
import { NextClawClientError } from '@nextclaw/client-sdk';
|
|
27
28
|
|
|
28
29
|
const MARKETPLACE_BACKGROUND_REFRESH_MS = 30_000;
|
|
30
|
+
export const MARKETPLACE_SKILL_LOCAL_CHANGES_ERROR_CODE = 'MARKETPLACE_SKILL_LOCAL_CHANGES';
|
|
31
|
+
|
|
32
|
+
export function isMarketplaceSkillLocalChangesError(error: unknown): boolean {
|
|
33
|
+
return error instanceof NextClawClientError
|
|
34
|
+
&& error.code === MARKETPLACE_SKILL_LOCAL_CHANGES_ERROR_CODE;
|
|
35
|
+
}
|
|
29
36
|
|
|
30
37
|
export function useMarketplaceItems(params: MarketplaceListParams) {
|
|
31
38
|
const query = useInfiniteQuery({
|
|
@@ -209,6 +216,9 @@ export function useManageMarketplaceItem() {
|
|
|
209
216
|
toast.success(result.message || fallback);
|
|
210
217
|
},
|
|
211
218
|
onError: (error: Error) => {
|
|
219
|
+
if (isMarketplaceSkillLocalChangesError(error)) {
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
212
222
|
toast.error(error.message || t('marketplaceOperationFailed'));
|
|
213
223
|
}
|
|
214
224
|
});
|
|
@@ -3,6 +3,10 @@ export { McpMarketplacePage } from './components/mcp/mcp-marketplace-page';
|
|
|
3
3
|
export {
|
|
4
4
|
MARKETPLACE_DETAIL_DOC_BROWSER_RENDERERS,
|
|
5
5
|
} from './components/marketplace-detail-doc';
|
|
6
|
+
export {
|
|
7
|
+
buildLocaleFallbacks,
|
|
8
|
+
pickLocalizedText,
|
|
9
|
+
} from './components/marketplace-localization';
|
|
6
10
|
export {
|
|
7
11
|
useInstallMarketplaceItem,
|
|
8
12
|
useManageMarketplaceItem,
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
export { PanelAppsList } from './components/panel-apps-list';
|
|
2
|
-
export {
|
|
2
|
+
export {
|
|
3
|
+
useGrantPanelAppClient,
|
|
4
|
+
usePanelApps,
|
|
5
|
+
useRecordPanelAppOpened,
|
|
6
|
+
} from './hooks/use-panel-apps';
|
|
3
7
|
export { PanelAppBridgeManager } from './managers/panel-app-bridge.manager';
|
|
4
8
|
export { findPanelAppEntryByDisplayId } from './utils/panel-app-entry-match.utils';
|
|
5
9
|
export { PANEL_APP_IFRAME_SANDBOX, focusPanelAppIframe } from './utils/panel-app-iframe.utils';
|
|
@@ -4,9 +4,16 @@ import {
|
|
|
4
4
|
} from '@/features/panel-apps/utils/panel-app-doc-browser.utils';
|
|
5
5
|
|
|
6
6
|
describe('panel app doc browser URL helpers', () => {
|
|
7
|
-
it('uses the root apps URL for the
|
|
8
|
-
expect(createAppsPanelUrl('
|
|
9
|
-
expect(getAppsPanelTabFromUrl('nextclaw://apps')).toBe('
|
|
7
|
+
it('uses the root apps URL for the package library', () => {
|
|
8
|
+
expect(createAppsPanelUrl('apps')).toBe('nextclaw://apps');
|
|
9
|
+
expect(getAppsPanelTabFromUrl('nextclaw://apps')).toBe('apps');
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
it('round-trips the developer panel apps tab', () => {
|
|
13
|
+
const url = createAppsPanelUrl('panel-apps');
|
|
14
|
+
|
|
15
|
+
expect(url).toBe('nextclaw://apps?tab=panel-apps');
|
|
16
|
+
expect(getAppsPanelTabFromUrl(url)).toBe('panel-apps');
|
|
10
17
|
});
|
|
11
18
|
|
|
12
19
|
it('round-trips the service apps tab through the hidden apps URL', () => {
|
|
@@ -16,8 +23,8 @@ describe('panel app doc browser URL helpers', () => {
|
|
|
16
23
|
expect(getAppsPanelTabFromUrl(url)).toBe('service-apps');
|
|
17
24
|
});
|
|
18
25
|
|
|
19
|
-
it('falls back to
|
|
20
|
-
expect(getAppsPanelTabFromUrl('nextclaw://apps?tab=unknown')).toBe('
|
|
21
|
-
expect(getAppsPanelTabFromUrl('not a url')).toBe('
|
|
26
|
+
it('falls back to the package library for unknown URL state', () => {
|
|
27
|
+
expect(getAppsPanelTabFromUrl('nextclaw://apps?tab=unknown')).toBe('apps');
|
|
28
|
+
expect(getAppsPanelTabFromUrl('not a url')).toBe('apps');
|
|
22
29
|
});
|
|
23
30
|
});
|
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
RIGHT_PANEL_APPS_URL,
|
|
11
11
|
RIGHT_PANEL_HOME_TAB_KIND,
|
|
12
12
|
RIGHT_PANEL_PANEL_APP_TAB_KIND,
|
|
13
|
+
RIGHT_PANEL_PANEL_APPS_URL,
|
|
13
14
|
RIGHT_PANEL_SERVICE_APPS_URL,
|
|
14
15
|
RightPanelResourceHomePage,
|
|
15
16
|
} from '@/features/right-panel-resources';
|
|
@@ -18,14 +19,16 @@ import { PANEL_APP_IFRAME_SANDBOX, focusPanelAppIframe } from './panel-app-ifram
|
|
|
18
19
|
|
|
19
20
|
export const APPS_TAB_KIND = RIGHT_PANEL_APPS_TAB_KIND;
|
|
20
21
|
export const PANEL_APP_TAB_KIND = RIGHT_PANEL_PANEL_APP_TAB_KIND;
|
|
21
|
-
const DEFAULT_APPS_PANEL_TAB: AppsPanelTab = '
|
|
22
|
+
const DEFAULT_APPS_PANEL_TAB: AppsPanelTab = 'apps';
|
|
22
23
|
|
|
23
24
|
function isAppsPanelTab(value: unknown): value is AppsPanelTab {
|
|
24
|
-
return value === 'panel-apps' || value === 'service-apps';
|
|
25
|
+
return value === 'apps' || value === 'panel-apps' || value === 'service-apps';
|
|
25
26
|
}
|
|
26
27
|
|
|
27
28
|
export function createAppsPanelUrl(tab: AppsPanelTab = DEFAULT_APPS_PANEL_TAB): string {
|
|
28
|
-
|
|
29
|
+
if (tab === 'panel-apps') return RIGHT_PANEL_PANEL_APPS_URL;
|
|
30
|
+
if (tab === 'service-apps') return RIGHT_PANEL_SERVICE_APPS_URL;
|
|
31
|
+
return RIGHT_PANEL_APPS_URL;
|
|
29
32
|
}
|
|
30
33
|
|
|
31
34
|
export function getAppsPanelTabFromUrl(url: string): AppsPanelTab {
|
|
@@ -6,19 +6,22 @@ export const RIGHT_PANEL_HOME_TAB_KIND = 'home';
|
|
|
6
6
|
export const RIGHT_PANEL_HOME_URL = 'nextclaw://new-tab';
|
|
7
7
|
export const RIGHT_PANEL_APPS_TAB_KIND = 'apps';
|
|
8
8
|
export const RIGHT_PANEL_APPS_URL = 'nextclaw://apps';
|
|
9
|
+
export const RIGHT_PANEL_PANEL_APPS_URL = `${RIGHT_PANEL_APPS_URL}?tab=panel-apps`;
|
|
9
10
|
export const RIGHT_PANEL_SERVICE_APPS_URL = `${RIGHT_PANEL_APPS_URL}?tab=service-apps`;
|
|
10
11
|
export const RIGHT_PANEL_PANEL_APP_TAB_KIND = 'panel-app';
|
|
11
12
|
|
|
12
|
-
export type RightPanelAppsTab = 'panel-apps' | 'service-apps';
|
|
13
|
+
export type RightPanelAppsTab = 'apps' | 'panel-apps' | 'service-apps';
|
|
13
14
|
|
|
14
|
-
const DEFAULT_RIGHT_PANEL_APPS_TAB: RightPanelAppsTab = '
|
|
15
|
+
const DEFAULT_RIGHT_PANEL_APPS_TAB: RightPanelAppsTab = 'apps';
|
|
15
16
|
|
|
16
17
|
function isRightPanelAppsTab(value: unknown): value is RightPanelAppsTab {
|
|
17
|
-
return value === 'panel-apps' || value === 'service-apps';
|
|
18
|
+
return value === 'apps' || value === 'panel-apps' || value === 'service-apps';
|
|
18
19
|
}
|
|
19
20
|
|
|
20
21
|
export function createRightPanelAppsUrl(tab: RightPanelAppsTab = DEFAULT_RIGHT_PANEL_APPS_TAB): string {
|
|
21
|
-
|
|
22
|
+
if (tab === 'panel-apps') return RIGHT_PANEL_PANEL_APPS_URL;
|
|
23
|
+
if (tab === 'service-apps') return RIGHT_PANEL_SERVICE_APPS_URL;
|
|
24
|
+
return RIGHT_PANEL_APPS_URL;
|
|
22
25
|
}
|
|
23
26
|
|
|
24
27
|
export function getRightPanelAppsTabFromUrl(url: string): RightPanelAppsTab {
|
|
@@ -11,6 +11,7 @@ const mocks = vi.hoisted(() => ({
|
|
|
11
11
|
start: vi.fn(),
|
|
12
12
|
stop: vi.fn(),
|
|
13
13
|
checkForUpdates: vi.fn(),
|
|
14
|
+
updateNow: vi.fn(),
|
|
14
15
|
downloadUpdate: vi.fn(),
|
|
15
16
|
applyDownloadedUpdate: vi.fn(),
|
|
16
17
|
updateChannel: vi.fn()
|
|
@@ -48,6 +49,7 @@ describe('DesktopUpdateConfig', () => {
|
|
|
48
49
|
mocks.start.mockReset();
|
|
49
50
|
mocks.stop.mockReset();
|
|
50
51
|
mocks.checkForUpdates.mockReset();
|
|
52
|
+
mocks.updateNow.mockReset();
|
|
51
53
|
mocks.downloadUpdate.mockReset();
|
|
52
54
|
mocks.applyDownloadedUpdate.mockReset();
|
|
53
55
|
mocks.updateChannel.mockReset();
|
|
@@ -101,12 +103,29 @@ describe('DesktopUpdateConfig', () => {
|
|
|
101
103
|
expect(screen.getByText('当前更新通道')).toBeTruthy();
|
|
102
104
|
expect(screen.getAllByText('Beta').length).toBeGreaterThan(0);
|
|
103
105
|
expect(screen.getByText('当前正在跟随 Beta 通道')).toBeTruthy();
|
|
104
|
-
expect(screen.getByText('
|
|
106
|
+
expect(screen.getByText('系统每两小时自动检查更新;是否开始更新由你确认。')).toBeTruthy();
|
|
105
107
|
expect(screen.queryByText('自动检查更新')).toBeNull();
|
|
106
108
|
expect(screen.queryByText('发现更新后自动后台下载')).toBeNull();
|
|
107
109
|
expect(screen.getByText('切回 Stable 后不会立刻强制降级;只有当 Stable 追平或超过当前版本时,才会继续提供 Stable 更新。')).toBeTruthy();
|
|
108
110
|
});
|
|
109
111
|
|
|
112
|
+
it('uses one update action instead of separate download and apply actions', async () => {
|
|
113
|
+
const user = userEvent.setup();
|
|
114
|
+
useRuntimeUpdateStore.setState((state) => ({
|
|
115
|
+
...state,
|
|
116
|
+
snapshot: state.snapshot ? { ...state.snapshot, status: 'update-available' } : null
|
|
117
|
+
}));
|
|
118
|
+
|
|
119
|
+
renderDesktopUpdateConfig();
|
|
120
|
+
|
|
121
|
+
expect(screen.queryByRole('button', { name: '下载更新' })).toBeNull();
|
|
122
|
+
const updateButton = screen.getByRole('button', { name: '立即更新' });
|
|
123
|
+
await user.click(updateButton);
|
|
124
|
+
expect(mocks.updateNow).toHaveBeenCalledTimes(1);
|
|
125
|
+
expect(mocks.downloadUpdate).not.toHaveBeenCalled();
|
|
126
|
+
expect(mocks.applyDownloadedUpdate).not.toHaveBeenCalled();
|
|
127
|
+
});
|
|
128
|
+
|
|
110
129
|
it('sends the newly selected release channel to the desktop update manager', async () => {
|
|
111
130
|
const user = userEvent.setup();
|
|
112
131
|
|
|
@@ -16,13 +16,15 @@ import {
|
|
|
16
16
|
type ReleaseNotesLocale,
|
|
17
17
|
type ReleaseNotesSection
|
|
18
18
|
} from '@/features/system-status/utils/update-release-notes.utils';
|
|
19
|
-
import {
|
|
19
|
+
import { RefreshCw, RotateCw } from 'lucide-react';
|
|
20
20
|
|
|
21
21
|
const STATUS_LABEL_KEYS: Record<string, string> = {
|
|
22
22
|
checking: 'desktopUpdatesStatusChecking',
|
|
23
23
|
'update-available': 'desktopUpdatesStatusAvailable',
|
|
24
24
|
downloading: 'desktopUpdatesStatusDownloading',
|
|
25
25
|
downloaded: 'desktopUpdatesStatusDownloaded',
|
|
26
|
+
applying: 'desktopUpdatesStatusApplying',
|
|
27
|
+
'restart-required': 'desktopUpdatesStatusRestartRequired',
|
|
26
28
|
'up-to-date': 'desktopUpdatesStatusUpToDate',
|
|
27
29
|
blocked: 'desktopUpdatesStatusBlocked',
|
|
28
30
|
failed: 'desktopUpdatesStatusFailed',
|
|
@@ -103,7 +105,7 @@ function getStatusLabel(snapshot: UpdateSnapshot): string {
|
|
|
103
105
|
return t(STATUS_LABEL_KEYS[snapshot.status] ?? 'desktopUpdatesStatusIdle');
|
|
104
106
|
}
|
|
105
107
|
function getStatusTone(status: string): string {
|
|
106
|
-
if (status === 'downloaded') {
|
|
108
|
+
if (status === 'downloaded' || status === 'restart-required') {
|
|
107
109
|
return 'bg-emerald-50 text-emerald-700 ring-emerald-100';
|
|
108
110
|
}
|
|
109
111
|
if (status === 'update-available' || status === 'downloading' || status === 'checking') {
|
|
@@ -218,9 +220,12 @@ export function DesktopUpdateConfig() {
|
|
|
218
220
|
const isChecking = busyAction === 'checking';
|
|
219
221
|
const isDownloading = busyAction === 'downloading';
|
|
220
222
|
const isApplying = busyAction === 'applying';
|
|
223
|
+
const isUpdating = busyAction === 'updating';
|
|
221
224
|
const isSwitchingChannel = busyAction === 'switching-channel';
|
|
222
|
-
const
|
|
223
|
-
|
|
225
|
+
const canUpdate = (snapshot.status === 'update-available' || snapshot.status === 'downloaded')
|
|
226
|
+
&& !isDownloading
|
|
227
|
+
&& !isApplying
|
|
228
|
+
&& !isUpdating;
|
|
224
229
|
const overviewStats = [
|
|
225
230
|
[t('runtimeUpdatesHostVersion'), formatVersion(snapshot.hostVersion)],
|
|
226
231
|
[t('desktopUpdatesCurrentBundleVersion'), formatVersion(snapshot.currentVersion)],
|
|
@@ -232,7 +237,7 @@ export function DesktopUpdateConfig() {
|
|
|
232
237
|
<SettingsPage
|
|
233
238
|
title={t('runtimeUpdatesPageTitle')}
|
|
234
239
|
description={t('runtimeUpdatesPageDescription')}
|
|
235
|
-
actions={<Button variant='outline' onClick={() => void runtimeUpdateManager.checkForUpdates()} disabled={isChecking || isDownloading || isApplying}><RefreshCw className={cn('mr-2 h-4 w-4', isChecking && 'animate-spin')} />{t('desktopUpdatesCheckNow')}</Button>}
|
|
240
|
+
actions={<Button variant='outline' onClick={() => void runtimeUpdateManager.checkForUpdates()} disabled={isChecking || isDownloading || isApplying || isUpdating}><RefreshCw className={cn('mr-2 h-4 w-4', isChecking && 'animate-spin')} />{t('desktopUpdatesCheckNow')}</Button>}
|
|
236
241
|
>
|
|
237
242
|
<SettingsSection title={t('desktopUpdatesOverviewTitle')} description={t('desktopUpdatesOverviewDescription')}>
|
|
238
243
|
<SettingsGroup>
|
|
@@ -278,7 +283,7 @@ export function DesktopUpdateConfig() {
|
|
|
278
283
|
layout='stacked'
|
|
279
284
|
>
|
|
280
285
|
<div className='space-y-2'>
|
|
281
|
-
<Select value={snapshot.channel} disabled={isSwitchingChannel || isChecking || isDownloading || isApplying} onValueChange={(value) => void runtimeUpdateManager.updateChannel(value as UpdateSnapshot['channel'])}>
|
|
286
|
+
<Select value={snapshot.channel} disabled={isSwitchingChannel || isChecking || isDownloading || isApplying || isUpdating} onValueChange={(value) => void runtimeUpdateManager.updateChannel(value as UpdateSnapshot['channel'])}>
|
|
282
287
|
<SelectTrigger className='w-full max-w-sm'>
|
|
283
288
|
<SelectValue placeholder={t('desktopUpdatesReleaseChannel')} />
|
|
284
289
|
</SelectTrigger>
|
|
@@ -295,16 +300,12 @@ export function DesktopUpdateConfig() {
|
|
|
295
300
|
<SettingsSection title={t('desktopUpdatesActionsTitle')} description={t('runtimeUpdatesActionsDescription')}>
|
|
296
301
|
<SettingsGroup>
|
|
297
302
|
<div className='flex flex-wrap items-center gap-3 p-4'>
|
|
298
|
-
<Button variant='outline' onClick={() => void runtimeUpdateManager.checkForUpdates()} disabled={isChecking || isDownloading || isApplying}>
|
|
303
|
+
<Button variant='outline' onClick={() => void runtimeUpdateManager.checkForUpdates()} disabled={isChecking || isDownloading || isApplying || isUpdating}>
|
|
299
304
|
<RefreshCw className={cn('mr-2 h-4 w-4', isChecking && 'animate-spin')} />
|
|
300
305
|
{t('desktopUpdatesCheckNow')}
|
|
301
306
|
</Button>
|
|
302
|
-
<Button onClick={() => void runtimeUpdateManager.
|
|
303
|
-
<
|
|
304
|
-
{t('desktopUpdatesDownloadNow')}
|
|
305
|
-
</Button>
|
|
306
|
-
<Button variant='secondary' onClick={() => void runtimeUpdateManager.applyDownloadedUpdate()} disabled={!canApply}>
|
|
307
|
-
<RotateCw className={cn('mr-2 h-4 w-4', isApplying && 'animate-spin')} />
|
|
307
|
+
<Button onClick={() => void runtimeUpdateManager.updateNow()} disabled={!canUpdate}>
|
|
308
|
+
<RotateCw className={cn('mr-2 h-4 w-4', isUpdating && 'animate-spin')} />
|
|
308
309
|
{t('runtimeUpdatesApplyNow')}
|
|
309
310
|
</Button>
|
|
310
311
|
</div>
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import type { UpdateSnapshot } from '@nextclaw/shared';
|
|
2
|
+
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
3
|
+
import { RuntimeUpdateManager } from '@/features/system-status/managers/runtime-update.manager';
|
|
4
|
+
import { useRuntimeUpdateStore } from '@/features/system-status/stores/runtime-update.store';
|
|
5
|
+
|
|
6
|
+
const mocks = vi.hoisted(() => ({
|
|
7
|
+
applyRuntimeUpdate: vi.fn(),
|
|
8
|
+
checkRuntimeUpdate: vi.fn(),
|
|
9
|
+
downloadRuntimeUpdate: vi.fn(),
|
|
10
|
+
fetchRuntimeUpdate: vi.fn(),
|
|
11
|
+
updateRuntimeUpdateChannel: vi.fn()
|
|
12
|
+
}));
|
|
13
|
+
|
|
14
|
+
vi.mock('@/shared/lib/api', () => mocks);
|
|
15
|
+
|
|
16
|
+
const createSnapshot = (patch: Partial<UpdateSnapshot> = {}): UpdateSnapshot => ({
|
|
17
|
+
status: 'update-available',
|
|
18
|
+
installationKind: 'npm-runtime-bundle',
|
|
19
|
+
channel: 'stable',
|
|
20
|
+
hostVersion: '0.30.0',
|
|
21
|
+
currentVersion: '0.30.0',
|
|
22
|
+
availableVersion: '0.31.0',
|
|
23
|
+
downloadedVersion: null,
|
|
24
|
+
minimumHostVersion: null,
|
|
25
|
+
releaseNotesUrl: null,
|
|
26
|
+
lastCheckedAt: null,
|
|
27
|
+
progress: null,
|
|
28
|
+
canApplyInApp: false,
|
|
29
|
+
requiresRestart: false,
|
|
30
|
+
blockReason: null,
|
|
31
|
+
recoveryCommand: null,
|
|
32
|
+
errorMessage: null,
|
|
33
|
+
...patch
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
describe('RuntimeUpdateManager', () => {
|
|
37
|
+
beforeEach(() => {
|
|
38
|
+
vi.clearAllMocks();
|
|
39
|
+
useRuntimeUpdateStore.setState({
|
|
40
|
+
supported: false,
|
|
41
|
+
initialized: false,
|
|
42
|
+
busyAction: null,
|
|
43
|
+
snapshot: null
|
|
44
|
+
});
|
|
45
|
+
mocks.fetchRuntimeUpdate.mockResolvedValue(createSnapshot());
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('downloads and applies an available update from one user action', async () => {
|
|
49
|
+
const downloaded = createSnapshot({
|
|
50
|
+
status: 'downloaded',
|
|
51
|
+
downloadedVersion: '0.31.0',
|
|
52
|
+
canApplyInApp: true
|
|
53
|
+
});
|
|
54
|
+
const applied = createSnapshot({
|
|
55
|
+
status: 'restart-required',
|
|
56
|
+
availableVersion: null,
|
|
57
|
+
downloadedVersion: null,
|
|
58
|
+
requiresRestart: true
|
|
59
|
+
});
|
|
60
|
+
mocks.downloadRuntimeUpdate.mockResolvedValue(downloaded);
|
|
61
|
+
mocks.applyRuntimeUpdate.mockResolvedValue(applied);
|
|
62
|
+
const manager = new RuntimeUpdateManager();
|
|
63
|
+
await manager.start();
|
|
64
|
+
|
|
65
|
+
await manager.updateNow();
|
|
66
|
+
|
|
67
|
+
expect(mocks.downloadRuntimeUpdate).toHaveBeenCalledTimes(1);
|
|
68
|
+
expect(mocks.applyRuntimeUpdate).toHaveBeenCalledTimes(1);
|
|
69
|
+
expect(useRuntimeUpdateStore.getState()).toMatchObject({
|
|
70
|
+
busyAction: null,
|
|
71
|
+
snapshot: applied
|
|
72
|
+
});
|
|
73
|
+
manager.stop();
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it('resumes a downloaded update without downloading it again', async () => {
|
|
77
|
+
const downloaded = createSnapshot({
|
|
78
|
+
status: 'downloaded',
|
|
79
|
+
downloadedVersion: '0.31.0',
|
|
80
|
+
canApplyInApp: true
|
|
81
|
+
});
|
|
82
|
+
const applied = createSnapshot({
|
|
83
|
+
status: 'restart-required',
|
|
84
|
+
availableVersion: null,
|
|
85
|
+
downloadedVersion: null,
|
|
86
|
+
requiresRestart: true
|
|
87
|
+
});
|
|
88
|
+
mocks.fetchRuntimeUpdate.mockResolvedValue(downloaded);
|
|
89
|
+
mocks.applyRuntimeUpdate.mockResolvedValue(applied);
|
|
90
|
+
const manager = new RuntimeUpdateManager();
|
|
91
|
+
await manager.start();
|
|
92
|
+
|
|
93
|
+
await manager.updateNow();
|
|
94
|
+
|
|
95
|
+
expect(mocks.downloadRuntimeUpdate).not.toHaveBeenCalled();
|
|
96
|
+
expect(mocks.applyRuntimeUpdate).toHaveBeenCalledTimes(1);
|
|
97
|
+
manager.stop();
|
|
98
|
+
});
|
|
99
|
+
});
|
|
@@ -208,6 +208,28 @@ export class RuntimeUpdateManager {
|
|
|
208
208
|
}
|
|
209
209
|
};
|
|
210
210
|
|
|
211
|
+
updateNow = async () => {
|
|
212
|
+
const initialSnapshot = useRuntimeUpdateStore.getState().snapshot;
|
|
213
|
+
if (initialSnapshot?.status !== 'update-available' && initialSnapshot?.status !== 'downloaded') {
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
try {
|
|
218
|
+
await this.runSnapshotCommand('updating', t('runtimeUpdatesUpdateFailed'), async (source) => {
|
|
219
|
+
const downloadedSnapshot = initialSnapshot.status === 'downloaded'
|
|
220
|
+
? initialSnapshot
|
|
221
|
+
: await source.downloadUpdate();
|
|
222
|
+
useRuntimeUpdateStore.setState({ snapshot: downloadedSnapshot });
|
|
223
|
+
if (downloadedSnapshot.status !== 'downloaded') {
|
|
224
|
+
return downloadedSnapshot;
|
|
225
|
+
}
|
|
226
|
+
return await source.applyDownloadedUpdate();
|
|
227
|
+
});
|
|
228
|
+
} catch {
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
};
|
|
232
|
+
|
|
211
233
|
updateChannel = async (channel: UpdateSnapshot['channel']) => {
|
|
212
234
|
const currentChannel = useRuntimeUpdateStore.getState().snapshot?.channel;
|
|
213
235
|
if (currentChannel === channel) {
|