@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.
Files changed (312) hide show
  1. package/CHANGELOG.md +39 -0
  2. package/dist/assets/{abnfDiagram-VRR7QNED-DvTjgVMf.js → abnfDiagram-VRR7QNED-BcFJp_1X.js} +1 -1
  3. package/dist/assets/{aiden0z-pptx-renderer.es-C1nfBAXR.js → aiden0z-pptx-renderer.es-Dp2ezlQn.js} +1 -1
  4. package/dist/assets/{api-BxPNDp30.js → api-CK3Fro7z.js} +6 -6
  5. package/dist/assets/appearance-settings-page-zqwWO8Dy.js +1 -0
  6. package/dist/assets/{arc-BvhODBXv.js → arc-CkgyPMyW.js} +1 -1
  7. package/dist/assets/architecture-TIHT7OUA-DO2x53FK.js +1 -0
  8. package/dist/assets/{architectureDiagram-ZJ3FMSHR-BKO9RgNS.js → architectureDiagram-ZJ3FMSHR-CMd4cIjI.js} +1 -1
  9. package/dist/assets/{blockDiagram-677ZJIJ3-DHpDXy7D.js → blockDiagram-677ZJIJ3-Bz3tbhet.js} +1 -1
  10. package/dist/assets/{c4Diagram-LMCZKHZV-BG_iQnDr.js → c4Diagram-LMCZKHZV-D9hFNAGv.js} +1 -1
  11. package/dist/assets/channel-nBvMaVyP.js +1 -0
  12. package/dist/assets/{channels-list-page-DRaNWAxC.js → channels-list-page-CVCZBlCL.js} +2 -2
  13. package/dist/assets/chat-page-D4wehRTz.js +1 -0
  14. package/dist/assets/chunk-2Q5K7J3B-CJgqFDS4.js +1 -0
  15. package/dist/assets/{chunk-32BRIVSS-DR9M0LLx.js → chunk-32BRIVSS-7NrjEITq.js} +1 -1
  16. package/dist/assets/{chunk-52WLFC77-698iC3BB.js → chunk-52WLFC77-viIcqj9q.js} +1 -1
  17. package/dist/assets/{chunk-5FCAYU7R-DikTj9Ey.js → chunk-5FCAYU7R-Vsu4J-VM.js} +1 -1
  18. package/dist/assets/{chunk-5HE753X5-ko0o7_q0.js → chunk-5HE753X5-C-w6CQZK.js} +1 -1
  19. package/dist/assets/{chunk-5JV3BV7I-CEHufhgo.js → chunk-5JV3BV7I-CuKgJQ4W.js} +1 -1
  20. package/dist/assets/{chunk-5TONJI2A-CGgno1cu.js → chunk-5TONJI2A-CYeiWZKb.js} +1 -1
  21. package/dist/assets/{chunk-5VM5RSS4-DkiB4cUQ.js → chunk-5VM5RSS4-DRCbfwIn.js} +1 -1
  22. package/dist/assets/{chunk-7BUUIJ7U-DzptcytQ.js → chunk-7BUUIJ7U-BvydOcm3.js} +1 -1
  23. package/dist/assets/{chunk-BIQX33UG-DMRSQJvT.js → chunk-BIQX33UG-RlppOXKo.js} +1 -1
  24. package/dist/assets/{chunk-C7G6YPKG-Bw1BwwuY.js → chunk-C7G6YPKG-DAizzYwr.js} +1 -1
  25. package/dist/assets/{chunk-CQNSW5MT-C0z6eHBk.js → chunk-CQNSW5MT-5eChJHca.js} +1 -1
  26. package/dist/assets/{chunk-CYSBUYHQ-ByPyAouO.js → chunk-CYSBUYHQ-YHPr53fv.js} +1 -1
  27. package/dist/assets/{chunk-EMLP6XTP-CJlAXWZg.js → chunk-EMLP6XTP-CK4D7Hz5.js} +1 -1
  28. package/dist/assets/{chunk-EX3LRPZG-Dqq4zKJu.js → chunk-EX3LRPZG-BmlDFIRW.js} +1 -1
  29. package/dist/assets/{chunk-FWX5IMBZ-B0SNoCmi.js → chunk-FWX5IMBZ-D0ksHZZ9.js} +2 -2
  30. package/dist/assets/{chunk-HOUHSVGY-DG_GJih_.js → chunk-HOUHSVGY-DPkq8FCm.js} +1 -1
  31. package/dist/assets/{chunk-ICXQ74PX-Drxk_6bE.js → chunk-ICXQ74PX-FAatv5fC.js} +1 -1
  32. package/dist/assets/{chunk-JG7HCLWE-DdLFPpnd.js → chunk-JG7HCLWE-DLkIi4F7.js} +1 -1
  33. package/dist/assets/{chunk-JWPE2WC7-B8M4G76F.js → chunk-JWPE2WC7-DawOjNE3.js} +1 -1
  34. package/dist/assets/{chunk-MOJQB5TN-XP6FJHY_.js → chunk-MOJQB5TN-Di52TorW.js} +1 -1
  35. package/dist/assets/{chunk-MOZMSUNE-C_6x18Qw.js → chunk-MOZMSUNE-PfyK9w1-.js} +1 -1
  36. package/dist/assets/{chunk-OGEWGWER-N25BzCmG.js → chunk-OGEWGWER-C3jdH3Ty.js} +1 -1
  37. package/dist/assets/{chunk-OSBZ3O6U-ASDEm7y0.js → chunk-OSBZ3O6U-CecHo-f0.js} +1 -1
  38. package/dist/assets/{chunk-PUDLZKDR-CTlvAcn7.js → chunk-PUDLZKDR-f3IJh3sN.js} +1 -1
  39. package/dist/assets/{chunk-Q4XR5HBZ-Da9BWfB6.js → chunk-Q4XR5HBZ-DQFeUPxZ.js} +1 -1
  40. package/dist/assets/{chunk-QBLGF6JB-Bi6RpTJO.js → chunk-QBLGF6JB-BhUeG0y2.js} +1 -1
  41. package/dist/assets/{chunk-R7FJI6CG-DkMsSPvK.js → chunk-R7FJI6CG-C3XpZ-2u.js} +1 -1
  42. package/dist/assets/{chunk-RYQCIY6F-DZp0eU-j.js → chunk-RYQCIY6F-CAqa5klQ.js} +1 -1
  43. package/dist/assets/{chunk-U6XO7XAA-vfinDSrj.js → chunk-U6XO7XAA-D_WXezeD.js} +1 -1
  44. package/dist/assets/{chunk-V7JOEXUC-C9Nksccs.js → chunk-V7JOEXUC-CZ70oOtp.js} +1 -1
  45. package/dist/assets/chunk-VAUOI2AC-BGvh3yIx.js +1 -0
  46. package/dist/assets/{chunk-VR4S4FIN-Xv6zfwXs.js → chunk-VR4S4FIN-B7AHEYYk.js} +1 -1
  47. package/dist/assets/{chunk-WYO6CB5R-mOJNzbWl.js → chunk-WYO6CB5R-Dn9To_OI.js} +2 -2
  48. package/dist/assets/chunk-XXDRQBXY-DaBxG5gs.js +1 -0
  49. package/dist/assets/{chunk-YOTPTUD7-1-aKSu4o.js → chunk-YOTPTUD7-BYEgRBk3.js} +1 -1
  50. package/dist/assets/{chunk-ZGVPDNZ5-OJ8veMpK.js → chunk-ZGVPDNZ5-C6YtZDU0.js} +1 -1
  51. package/dist/assets/{chunk-ZIRB5QZD-SR_Nq_PP.js → chunk-ZIRB5QZD-DMuYtnHF.js} +1 -1
  52. package/dist/assets/classDiagram-OUVF2IWQ-P6N03G2R.js +1 -0
  53. package/dist/assets/classDiagram-v2-EOCWNBFH-B1_uLvMv.js +1 -0
  54. package/dist/assets/config-split-page-CceNFvlY.js +1 -0
  55. package/dist/assets/confirm-dialog-CWeNYWZY.js +5 -0
  56. package/dist/assets/{cose-bilkent-JH36ORCC-NjVgz2Bw.js → cose-bilkent-JH36ORCC-Bc2Ex5Y3.js} +1 -1
  57. package/dist/assets/cynefin-VYW2F7L2-B-H8Qp-n.js +1 -0
  58. package/dist/assets/{cynefinDiagram-TSTJHNR4-B_17LDqD.js → cynefinDiagram-TSTJHNR4-DVysQoGz.js} +1 -1
  59. package/dist/assets/{dagre-CMe101gk.js → dagre-CMhQ1cAG.js} +1 -1
  60. package/dist/assets/{dagre-VKFMJZFB-qo8nLb3B.js → dagre-VKFMJZFB-RYyWpPJZ.js} +1 -1
  61. package/dist/assets/desktop-update-config-Dirc3bpt.js +1 -0
  62. package/dist/assets/{diagram-FQU43EPY-Ba3XIklC.js → diagram-FQU43EPY-BD6KONdF.js} +1 -1
  63. package/dist/assets/{diagram-G47NLZAW-CG45TcUx.js → diagram-G47NLZAW-CGB2ptRR.js} +1 -1
  64. package/dist/assets/{diagram-NH7WQ7WH-2F2HrwV-.js → diagram-NH7WQ7WH-DnkOI6cU.js} +1 -1
  65. package/dist/assets/{diagram-OA4YK3LP-CXfWH9NZ.js → diagram-OA4YK3LP-DF82RZ7P.js} +1 -1
  66. package/dist/assets/{diagram-WEI45ONY-CH_4yepc.js → diagram-WEI45ONY-Be2v7Ck9.js} +1 -1
  67. package/dist/assets/{dist-B4Pf5X3Q.js → dist-AYxkFx2B.js} +1 -1
  68. package/dist/assets/{dist-DHEWkGWX.js → dist-D9xtGgdL.js} +1 -1
  69. package/dist/assets/doc-browser-4Sud6dWw.js +1 -0
  70. package/dist/assets/doc-browser-D4x_yH5F.js +1 -0
  71. package/dist/assets/doc-browser-context-Cb7Z0j90.js +1 -0
  72. package/dist/assets/{docx-preview-CoM4JK7_.js → docx-preview-BqwAjrj3.js} +1 -1
  73. package/dist/assets/{ebnfDiagram-CCIWWBDH-BwiG4Ea1.js → ebnfDiagram-CCIWWBDH-CP98OYk8.js} +1 -1
  74. package/dist/assets/{erDiagram-Q63AITRT-BML3hpnQ.js → erDiagram-Q63AITRT-ZLlx8xfs.js} +1 -1
  75. package/dist/assets/eventmodeling-45OFAUF4-YVtShyFA.js +1 -0
  76. package/dist/assets/flowDiagram-23GEKE2U-BeKXabVO.js +1 -0
  77. package/dist/assets/form-actions-B2q4jgjE.js +1 -0
  78. package/dist/assets/{ganttDiagram-NO4QXBWP-80enfWbD.js → ganttDiagram-NO4QXBWP-DXCDjHR2.js} +1 -1
  79. package/dist/assets/gitGraph-TEB2WS4Q-jTSiBj-p.js +1 -0
  80. package/dist/assets/{gitGraphDiagram-IHSO6WYX-CVLqXr-b.js → gitGraphDiagram-IHSO6WYX-l2HWrTJp.js} +1 -1
  81. package/dist/assets/index-CdyTsSBu.css +1 -0
  82. package/dist/assets/index-Das_bw2h.js +107 -0
  83. package/dist/assets/info-DKCQHKI2-BIporNWb.js +1 -0
  84. package/dist/assets/{infoDiagram-FWYZ7A6U-Dyvs2t0i.js → infoDiagram-FWYZ7A6U-D-DjX-bn.js} +1 -1
  85. package/dist/assets/{ishikawaDiagram-FXEZZL3T-sSnxDKfh.js → ishikawaDiagram-FXEZZL3T-y_Xy128F.js} +1 -1
  86. package/dist/assets/{journeyDiagram-5HDEW3XC-D22V71Mf.js → journeyDiagram-5HDEW3XC-DJn5xYcQ.js} +1 -1
  87. package/dist/assets/{kanban-definition-HUTT4EX6-D3ds0p-g.js → kanban-definition-HUTT4EX6-DJVIOR21.js} +1 -1
  88. package/dist/assets/{line-CbyFs065.js → line-Cos7PIOj.js} +1 -1
  89. package/dist/assets/{linear-4DKtnZBS.js → linear-BtDLqtzF.js} +1 -1
  90. package/dist/assets/loader-circle-CpmBdaon.js +1 -0
  91. package/dist/assets/{map-CcZzEoz6.js → map-BKnQyvd0.js} +1 -1
  92. package/dist/assets/mcp-marketplace-page-CdOY3Sop.js +1 -0
  93. package/dist/assets/mcp-marketplace-page-DvjrfqEt.js +9 -0
  94. package/dist/assets/{mermaid-parser.core-DPmMOjXD.js → mermaid-parser.core-XUNZhr2W.js} +2 -2
  95. package/dist/assets/{mermaid.core-BSf-ddRa.js → mermaid.core-ySrvolIe.js} +3 -3
  96. package/dist/assets/{mindmap-definition-LN4V7U3C-BUTwWPyx.js → mindmap-definition-LN4V7U3C-BypL9tei.js} +1 -1
  97. package/dist/assets/model-config-page-DfOLyQqR.js +1 -0
  98. package/dist/assets/{navigation-link-CAFOcXNg.js → navigation-link-MiiyoIjH.js} +2 -2
  99. package/dist/assets/{ordinal-BgLYtOZE.js → ordinal-CxxVWxYU.js} +1 -1
  100. package/dist/assets/packet-7NZHBO7P-CuFMADV0.js +1 -0
  101. package/dist/assets/{pegDiagram-2B236MQR-BEu_ZBjc.js → pegDiagram-2B236MQR-D-U2KBGv.js} +1 -1
  102. package/dist/assets/pie-RZYD4A2V-XUeIXhM9.js +1 -0
  103. package/dist/assets/{pieDiagram-ENE6RG2P-CAwAwuBd.js → pieDiagram-ENE6RG2P-CBi3GusJ.js} +1 -1
  104. package/dist/assets/plus-C8FkjpSZ.js +1 -0
  105. package/dist/assets/provider-scoped-model-input-DXzjr5l0.js +1 -0
  106. package/dist/assets/providers-config-page-CaO8WfZH.js +1 -0
  107. package/dist/assets/{quadrantDiagram-ABIIQ3AL-CwE6F-_2.js → quadrantDiagram-ABIIQ3AL-H0ZuvnZL.js} +1 -1
  108. package/dist/assets/radar-I7S5WNFK-D-NNGh1k.js +1 -0
  109. package/dist/assets/railroad-3IZDKUUU-C67_uDaM.js +1 -0
  110. package/dist/assets/railroad-abnf-AHOZXSZD-BZ8Yflnd.js +1 -0
  111. package/dist/assets/railroad-ebnf-EBAXGLYW-B7zO7RoK.js +1 -0
  112. package/dist/assets/railroad-peg-LSFZ7HO6-R63PTgLz.js +1 -0
  113. package/dist/assets/{railroadDiagram-RFXS5EU6-xgLekU3B.js → railroadDiagram-RFXS5EU6-D7Y2EqSd.js} +1 -1
  114. package/dist/assets/react-C8XFFvuz.js +8 -0
  115. package/dist/assets/{refresh-cw-CWDp_JnN.js → refresh-cw-a2VVeeAF.js} +1 -1
  116. package/dist/assets/remote-BJHXa4mB.js +1 -0
  117. package/dist/assets/{requirementDiagram-TGXJPOKE-CyVUzEX5.js → requirementDiagram-TGXJPOKE-BiXNX1NN.js} +1 -1
  118. package/dist/assets/rotate-cw-DJ7UF3F2.js +1 -0
  119. package/dist/assets/runtime-config-page-BuA4A02z.js +1 -0
  120. package/dist/assets/{sankeyDiagram-HTMAVEWB-JK6bFmYB.js → sankeyDiagram-HTMAVEWB-qzDeLztW.js} +1 -1
  121. package/dist/assets/{save-Cnr71JON.js → save-CGm23Yww.js} +1 -1
  122. package/dist/assets/{search-CCX11X2m.js → search-CH2t--sH.js} +1 -1
  123. package/dist/assets/search-config-page-BAmYqoAz.js +1 -0
  124. package/dist/assets/secrets-config-page-BlPOuql_.js +3 -0
  125. package/dist/assets/security-config-CbDiY2xr.js +1 -0
  126. package/dist/assets/{select-DvwBu99K.js → select-DnxxDpcV.js} +2 -2
  127. package/dist/assets/{sequenceDiagram-DBY2YBRQ-BR5IokXV.js → sequenceDiagram-DBY2YBRQ-BtRbPQtL.js} +1 -1
  128. package/dist/assets/{settings-2-Dmv6zMne.js → settings-2-agnMYc8a.js} +1 -1
  129. package/dist/assets/{sizeCapture-X5ZJPWSS-SxG_YnMA.js → sizeCapture-X5ZJPWSS-CMYvM3SL.js} +1 -1
  130. package/dist/assets/skeleton-XpqeWL62.js +1 -0
  131. package/dist/assets/{src-vgz8E_-O.js → src-Bw-mzbQb.js} +1 -1
  132. package/dist/assets/{stateDiagram-2N3HPSRC-DD4OoYhn.js → stateDiagram-2N3HPSRC-BJ-mFeOz.js} +1 -1
  133. package/dist/assets/stateDiagram-v2-6OUMAXLB-BE-HqxOK.js +1 -0
  134. package/dist/assets/{swimlanes-5IMT3BWC-BWkFeyCT.js → swimlanes-5IMT3BWC-BsucVOO2.js} +2 -2
  135. package/dist/assets/swimlanesDiagram-G3AALYLV-Dr1H9LJH.js +8 -0
  136. package/dist/assets/{tag-chip-gMPvx7n_.js → tag-chip-CPOc8b2N.js} +1 -1
  137. package/dist/assets/{timeline-definition-FHXFAJF6-VTRPy6KA.js → timeline-definition-FHXFAJF6-Bicewlzo.js} +1 -1
  138. package/dist/assets/treeView-QDETBFTQ-npJ5dbqS.js +1 -0
  139. package/dist/assets/treemap-6X3UGDF4-pYrBx6oa.js +1 -0
  140. package/dist/assets/{vennDiagram-L72KCM5P-Bvk42Pek.js → vennDiagram-L72KCM5P-BxOOnO9x.js} +1 -1
  141. package/dist/assets/wardley-OPB4EBWU-CmSRKLR8.js +1 -0
  142. package/dist/assets/{wardleyDiagram-EHGQE667-DOFjx2s3.js → wardleyDiagram-EHGQE667-Cl7NltMv.js} +1 -1
  143. package/dist/assets/x-odTHAtLZ.js +1 -0
  144. package/dist/assets/{xychartDiagram-FW5EYKEG-DY5yRTUc.js → xychartDiagram-FW5EYKEG-DiMIxQ7N.js} +1 -1
  145. package/dist/index.html +21 -22
  146. package/package.json +8 -8
  147. package/src/features/apps/components/__tests__/app-packages-panel.test.tsx +234 -0
  148. package/src/features/apps/components/app-marketplace-dialog.tsx +262 -0
  149. package/src/features/apps/components/app-package-card.tsx +268 -0
  150. package/src/features/apps/components/app-packages-panel.tsx +301 -0
  151. package/src/features/apps/components/apps-panel.tsx +11 -4
  152. package/src/features/apps/hooks/use-app-marketplace.ts +95 -0
  153. package/src/features/apps/hooks/use-app-packages.ts +59 -0
  154. package/src/features/chat/components/chat-sidebar-list-mode-switch.tsx +27 -4
  155. package/src/features/chat/components/layout/__tests__/chat-sidebar-session-area.test.tsx +36 -5
  156. package/src/features/chat/components/layout/chat-sidebar-desktop-layout.tsx +12 -13
  157. package/src/features/chat/features/conversation/components/__tests__/session-conversation-area.test.tsx +79 -2
  158. package/src/features/chat/features/conversation/components/__tests__/session-conversation-input.streaming.test.tsx +4 -4
  159. package/src/features/chat/features/conversation/components/session-conversation-area.tsx +26 -5
  160. package/src/features/chat/features/conversation/components/session-conversation-input.tsx +3 -0
  161. package/src/features/chat/features/conversation/hooks/use-system-object-reference-select.ts +27 -0
  162. package/src/features/chat/features/conversation/utils/session-message-composer.utils.ts +5 -0
  163. package/src/features/chat/features/input/hooks/__tests__/use-chat-input-surface-state.test.tsx +102 -0
  164. package/src/features/chat/features/input/hooks/use-chat-input-surface-state.ts +71 -120
  165. package/src/features/chat/features/input/input-surface-plugins/__tests__/context-reference-plugin.test.ts +265 -18
  166. package/src/features/chat/features/input/input-surface-plugins/chat-input-product-plugin-adapters.types.ts +7 -2
  167. package/src/features/chat/features/input/input-surface-plugins/chat-input-product-plugin-adapters.utils.ts +124 -0
  168. package/src/features/chat/features/input/input-surface-plugins/context-reference-items.utils.ts +99 -15
  169. package/src/features/chat/features/input/input-surface-plugins/context-reference-plugin.utils.ts +143 -139
  170. package/src/features/chat/features/input/input-surface-plugins/panel-app-input-surface-items.utils.ts +116 -0
  171. package/src/features/chat/features/input/input-surface-plugins/slash-command-plugin.utils.ts +1 -1
  172. package/src/features/chat/features/input/utils/__tests__/chat-inline-token.utils.test.ts +53 -1
  173. package/src/features/chat/features/input/utils/chat-composer-token-protocol.utils.ts +5 -0
  174. package/src/features/chat/features/input/utils/chat-inline-token-composer.utils.ts +134 -0
  175. package/src/features/chat/features/input/utils/chat-inline-token.utils.ts +30 -101
  176. package/src/features/chat/features/input/utils/chat-system-object-reference.utils.test.ts +44 -0
  177. package/src/features/chat/features/input/utils/chat-system-object-reference.utils.ts +46 -0
  178. package/src/features/chat/features/ncp/hooks/__tests__/use-hydrated-ncp-agent.test.tsx +40 -3
  179. package/src/features/chat/features/workspace/components/__tests__/chat-session-workspace-file-preview.selection.test.tsx +66 -0
  180. package/src/features/chat/features/workspace/components/__tests__/chat-session-workspace-file-preview.test.tsx +13 -1
  181. package/src/features/chat/features/workspace/components/__tests__/chat-session-workspace-panel-content.test.tsx +653 -90
  182. package/src/features/chat/features/workspace/components/__tests__/chat-session-workspace-text-selection.test.tsx +12 -8
  183. package/src/features/chat/features/workspace/components/chat-session-workspace-directory-browser.tsx +401 -288
  184. package/src/features/chat/features/workspace/components/chat-session-workspace-file-breadcrumbs.tsx +54 -35
  185. package/src/features/chat/features/workspace/components/chat-session-workspace-file-preview.tsx +79 -148
  186. package/src/features/chat/features/workspace/components/chat-session-workspace-panel-content.tsx +156 -94
  187. package/src/features/chat/features/workspace/components/project-files/__tests__/workspace-directory-entry-menu.test.tsx +50 -0
  188. package/src/features/chat/features/workspace/components/project-files/workspace-directory-entry-menu.tsx +209 -0
  189. package/src/features/chat/features/workspace/components/project-files/workspace-directory-tree.tsx +459 -0
  190. package/src/features/chat/features/workspace/components/project-files/workspace-new-folder-tree-item.tsx +112 -0
  191. package/src/features/chat/features/workspace/components/project-files/workspace-project-files-toolbar.tsx +64 -0
  192. package/src/features/chat/features/workspace/hooks/use-workspace-explorer-layout.ts +85 -0
  193. package/src/features/chat/features/workspace/hooks/use-workspace-file-actions.ts +357 -0
  194. package/src/features/chat/features/workspace/utils/chat-workspace-file-path-mutation.utils.ts +58 -0
  195. package/src/features/chat/features/workspace/utils/chat-workspace-file-tab.utils.ts +23 -22
  196. package/src/features/chat/features/workspace/utils/chat-workspace-panel-layout.utils.ts +13 -4
  197. package/src/features/chat/index.ts +1 -1
  198. package/src/features/chat/managers/__tests__/chat-composer-intent.manager.test.ts +86 -60
  199. package/src/features/chat/managers/__tests__/chat-thread.manager.test.ts +103 -99
  200. package/src/features/chat/managers/chat-composer-intent.manager.ts +37 -33
  201. package/src/features/chat/managers/chat-draft-intent.manager.ts +18 -2
  202. package/src/features/chat/managers/chat-thread.manager.ts +132 -128
  203. package/src/features/chat/stores/__tests__/chat-thread.store.test.ts +26 -40
  204. package/src/features/chat/stores/chat-thread.store.ts +53 -74
  205. package/src/features/inbox/components/inbox-reader-dialog.test.tsx +38 -5
  206. package/src/features/inbox/components/inbox-reader-dialog.tsx +5 -3
  207. package/src/features/inbox/managers/inbox.manager.test.ts +28 -3
  208. package/src/features/inbox/managers/inbox.manager.ts +14 -4
  209. package/src/features/inbox/pages/inbox-page.test.ts +121 -2
  210. package/src/features/inbox/pages/inbox-page.tsx +5 -3
  211. package/src/features/marketplace/components/__tests__/marketplace-page.test.tsx +85 -1
  212. package/src/features/marketplace/hooks/__tests__/use-marketplace.test.tsx +44 -1
  213. package/src/features/marketplace/hooks/use-marketplace-item-actions.ts +23 -2
  214. package/src/features/marketplace/hooks/use-marketplace.ts +10 -0
  215. package/src/features/marketplace/index.ts +4 -0
  216. package/src/features/panel-apps/index.ts +5 -1
  217. package/src/features/panel-apps/utils/__tests__/panel-app-doc-browser.utils.test.ts +13 -6
  218. package/src/features/panel-apps/utils/panel-app-doc-browser.utils.tsx +6 -3
  219. package/src/features/right-panel-resources/index.ts +1 -0
  220. package/src/features/right-panel-resources/utils/right-panel-resource-uri.utils.ts +7 -4
  221. package/src/features/system-status/components/__tests__/desktop-update-config.test.tsx +20 -1
  222. package/src/features/system-status/components/desktop-update-config.tsx +14 -13
  223. package/src/features/system-status/managers/__tests__/runtime-update.manager.test.ts +99 -0
  224. package/src/features/system-status/managers/runtime-update.manager.ts +22 -0
  225. package/src/features/system-status/stores/runtime-update.store.ts +1 -0
  226. package/src/shared/components/resizable-right-panel/__tests__/resizable-right-panel.test.tsx +35 -38
  227. package/src/shared/components/resizable-right-panel/resizable-right-panel.tsx +25 -19
  228. package/src/shared/components/ui/actions/__tests__/icon-action-button.test.tsx +15 -0
  229. package/src/shared/components/ui/actions/icon-action-button.tsx +4 -2
  230. package/src/shared/components/ui/context-menu/__tests__/context-menu.test.tsx +60 -34
  231. package/src/shared/components/ui/context-menu/context-menu.tsx +95 -68
  232. package/src/shared/hooks/use-system-object-references.ts +22 -0
  233. package/src/shared/lib/api/managers/client.manager.ts +31 -17
  234. package/src/shared/lib/api/server-path/server-path.types.ts +66 -0
  235. package/src/shared/lib/api/types.ts +155 -116
  236. package/src/shared/lib/api/utils/server-path.utils.ts +35 -11
  237. package/src/shared/lib/i18n/locales/en-US/chat.json +58 -9
  238. package/src/shared/lib/i18n/locales/en-US/desktop-update.json +7 -4
  239. package/src/shared/lib/i18n/locales/en-US/doc-browser.json +40 -0
  240. package/src/shared/lib/i18n/locales/en-US/marketplace.json +3 -0
  241. package/src/shared/lib/i18n/locales/zh-CN/chat.json +60 -11
  242. package/src/shared/lib/i18n/locales/zh-CN/desktop-update.json +7 -4
  243. package/src/shared/lib/i18n/locales/zh-CN/doc-browser.json +40 -0
  244. package/src/shared/lib/i18n/locales/zh-CN/marketplace.json +3 -0
  245. package/dist/assets/appearance-settings-page-DpVJjvkB.js +0 -1
  246. package/dist/assets/architecture-TIHT7OUA-Bjp9jf0h.js +0 -1
  247. package/dist/assets/channel-5T3pdR-s.js +0 -1
  248. package/dist/assets/chat-page-BuFDmG5_.js +0 -1
  249. package/dist/assets/chunk-2Q5K7J3B-DTZ3tGfE.js +0 -1
  250. package/dist/assets/chunk-VAUOI2AC-HLGIHQ6k.js +0 -1
  251. package/dist/assets/chunk-XXDRQBXY-Dl0m6Ni-.js +0 -1
  252. package/dist/assets/classDiagram-OUVF2IWQ-DcDnzGNX.js +0 -1
  253. package/dist/assets/classDiagram-v2-EOCWNBFH-BKXKMzKM.js +0 -1
  254. package/dist/assets/config-split-page-BVWVDbvG.js +0 -1
  255. package/dist/assets/confirm-dialog-mA2iVzxh.js +0 -5
  256. package/dist/assets/cynefin-VYW2F7L2-BWg7U2UD.js +0 -1
  257. package/dist/assets/desktop-update-config-DyQb1LXr.js +0 -1
  258. package/dist/assets/doc-browser-CtO_HtRy.js +0 -1
  259. package/dist/assets/doc-browser-context-CuGI6PQc.js +0 -1
  260. package/dist/assets/doc-browser-eMUagyBV.js +0 -1
  261. package/dist/assets/eventmodeling-45OFAUF4-SB-J4tpq.js +0 -1
  262. package/dist/assets/flowDiagram-23GEKE2U-BQ_u-eLM.js +0 -1
  263. package/dist/assets/form-actions-BOfIyG3O.js +0 -1
  264. package/dist/assets/gitGraph-TEB2WS4Q-DPxNwsFd.js +0 -1
  265. package/dist/assets/index-BjBdWHV1.js +0 -107
  266. package/dist/assets/index-DmpmjAGQ.css +0 -1
  267. package/dist/assets/info-DKCQHKI2-DNlNGH90.js +0 -1
  268. package/dist/assets/loader-circle-Df8wF2Pr.js +0 -1
  269. package/dist/assets/mcp-marketplace-page-DI49IKxp.js +0 -1
  270. package/dist/assets/mcp-marketplace-page-ofDqbOIF.js +0 -9
  271. package/dist/assets/model-config-page-D73Tu3jW.js +0 -1
  272. package/dist/assets/packet-7NZHBO7P-BpeiqkBZ.js +0 -1
  273. package/dist/assets/pie-RZYD4A2V-Cy1yxsPd.js +0 -1
  274. package/dist/assets/plus-DVU5RLCS.js +0 -1
  275. package/dist/assets/provider-scoped-model-input-BaJJJYMX.js +0 -1
  276. package/dist/assets/providers-config-page-BL6IRVBm.js +0 -1
  277. package/dist/assets/radar-I7S5WNFK-CEcPKCj8.js +0 -1
  278. package/dist/assets/railroad-3IZDKUUU-CW38eNDs.js +0 -1
  279. package/dist/assets/railroad-abnf-AHOZXSZD-ByrylcXf.js +0 -1
  280. package/dist/assets/railroad-ebnf-EBAXGLYW-CNB6GwP-.js +0 -1
  281. package/dist/assets/railroad-peg-LSFZ7HO6-x6hnqlBG.js +0 -1
  282. package/dist/assets/react-BC_orpAj.js +0 -8
  283. package/dist/assets/remote-T24O58lH.js +0 -1
  284. package/dist/assets/rotate-cw-BqhwSV5p.js +0 -1
  285. package/dist/assets/runtime-config-page-CXUx017t.js +0 -1
  286. package/dist/assets/search-config-page-BB6d5gT7.js +0 -1
  287. package/dist/assets/secrets-config-page-DYsDeT9K.js +0 -3
  288. package/dist/assets/security-config-CVPgqL7G.js +0 -1
  289. package/dist/assets/skeleton-C5tWata3.js +0 -1
  290. package/dist/assets/stateDiagram-v2-6OUMAXLB-ZC6dFORv.js +0 -1
  291. package/dist/assets/swimlanesDiagram-G3AALYLV-CRkh51Im.js +0 -8
  292. package/dist/assets/treeView-QDETBFTQ-Bw0DSCH7.js +0 -1
  293. package/dist/assets/treemap-6X3UGDF4-DFWBFcfh.js +0 -1
  294. package/dist/assets/use-confirm-dialog-D_3-QpZU.js +0 -1
  295. package/dist/assets/wardley-OPB4EBWU-CoWturk0.js +0 -1
  296. package/dist/assets/x-BEWSqeux.js +0 -1
  297. /package/dist/assets/{array-NADKd9aU.js → array-BQVzeGRt.js} +0 -0
  298. /package/dist/assets/{chunk-KEIR6QF5-BuyuzJJL.js → chunk-KEIR6QF5-f9psyCXK.js} +0 -0
  299. /package/dist/assets/{chunk-Y2CYZVJY-D_B2ZRNt.js → chunk-Y2CYZVJY-D1mAU_Po.js} +0 -0
  300. /package/dist/assets/{config-hints-BcfVUMij.js → config-hints-nV5HPOvq.js} +0 -0
  301. /package/dist/assets/{cytoscape.esm-BANTlgza.js → cytoscape.esm-0uDtdD0l.js} +0 -0
  302. /package/dist/assets/{defaultLocale-C4_L0aEs.js → defaultLocale-DSWXGgt0.js} +0 -0
  303. /package/dist/assets/{dist-DAPEIJbz.js → dist--ta5rX12.js} +0 -0
  304. /package/dist/assets/{graphlib-ChqXg0dI.js → graphlib-CQ6QV4yP.js} +0 -0
  305. /package/dist/assets/{host-capabilities-D9C4CBK5.js → host-capabilities-Tk038i04.js} +0 -0
  306. /package/dist/assets/{init-D8wZg9xq.js → init-Y6h9jGtZ.js} +0 -0
  307. /package/dist/assets/{jszip.min-CkbBfwAQ.js → jszip.min-8hyJpgpx.js} +0 -0
  308. /package/dist/assets/{katex-TZJFa0J_.js → katex-ARhY9PS1.js} +0 -0
  309. /package/dist/assets/{middleware-B3k-TSmZ.js → middleware-DXsbf9xI.js} +0 -0
  310. /package/dist/assets/{path-DodfxcX7.js → path-10BjdyLh.js} +0 -0
  311. /package/dist/assets/{rough.esm-CUaa1Rbe.js → rough.esm-DxgC_14r.js} +0 -0
  312. /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
- await manageMutation.mutateAsync({
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 { usePanelApps } from './hooks/use-panel-apps';
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 default panel apps tab', () => {
8
- expect(createAppsPanelUrl('panel-apps')).toBe('nextclaw://apps');
9
- expect(getAppsPanelTabFromUrl('nextclaw://apps')).toBe('panel-apps');
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 panel apps for unknown URL state', () => {
20
- expect(getAppsPanelTabFromUrl('nextclaw://apps?tab=unknown')).toBe('panel-apps');
21
- expect(getAppsPanelTabFromUrl('not a url')).toBe('panel-apps');
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 = 'panel-apps';
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
- return tab === DEFAULT_APPS_PANEL_TAB ? RIGHT_PANEL_APPS_URL : RIGHT_PANEL_SERVICE_APPS_URL;
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 {
@@ -15,5 +15,6 @@ export {
15
15
  RIGHT_PANEL_HOME_TAB_KIND,
16
16
  RIGHT_PANEL_HOME_URL,
17
17
  RIGHT_PANEL_PANEL_APP_TAB_KIND,
18
+ RIGHT_PANEL_PANEL_APPS_URL,
18
19
  RIGHT_PANEL_SERVICE_APPS_URL,
19
20
  } from '@/features/right-panel-resources/utils/right-panel-resource-uri.utils';
@@ -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 = 'panel-apps';
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
- return tab === DEFAULT_RIGHT_PANEL_APPS_TAB ? RIGHT_PANEL_APPS_URL : RIGHT_PANEL_SERVICE_APPS_URL;
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('系统每两小时自动检查更新,下载和应用都由你手动确认。')).toBeTruthy();
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 { Download, RefreshCw, RotateCw } from 'lucide-react';
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 canDownload = snapshot.status === 'update-available' && !isDownloading && !isApplying;
223
- const canApply = snapshot.status === 'downloaded' && !isApplying;
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.downloadUpdate()} disabled={!canDownload}>
303
- <Download className={cn('mr-2 h-4 w-4', isDownloading && 'animate-bounce')} />
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) {
@@ -5,6 +5,7 @@ export type RuntimeUpdateBusyAction =
5
5
  | 'checking'
6
6
  | 'downloading'
7
7
  | 'applying'
8
+ | 'updating'
8
9
  | 'switching-channel'
9
10
  | null;
10
11