@nextclaw/ui 0.15.25 → 0.15.27

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 (334) hide show
  1. package/CHANGELOG.md +34 -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-FK7tikI1.js → api-a_GaOFc7.js} +6 -6
  5. package/dist/assets/appearance-settings-page-DlGXLgff.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-CocdcIko.js → channels-list-page-BEEk4Uc9.js} +2 -2
  13. package/dist/assets/chat-page-DUQXXqQm.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-ps1Aod3g.js +1 -0
  55. package/dist/assets/confirm-dialog-DJSUNG33.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--zagjHic.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-CaYdVVeO.js → dist-BkTYNzm_.js} +1 -1
  68. package/dist/assets/{dist-BH7W5yCi.js → dist-DkR7_Dpl.js} +1 -1
  69. package/dist/assets/doc-browser-DahzJEWj.js +1 -0
  70. package/dist/assets/doc-browser-Q67TOGVS.js +1 -0
  71. package/dist/assets/doc-browser-context-BQim8SqD.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-0c0aw_bl.js → form-actions-B2-bpFie.js} +1 -1
  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-Ba_o5fu9.js +109 -0
  82. package/dist/assets/index-DSLK7zgR.css +1 -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-DyXZU92l.js +1 -0
  91. package/dist/assets/{map-CcZzEoz6.js → map-BKnQyvd0.js} +1 -1
  92. package/dist/assets/mcp-marketplace-page-B0dBE5CV.js +1 -0
  93. package/dist/assets/mcp-marketplace-page-w8panjZG.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-rrhFaque.js +1 -0
  98. package/dist/assets/{navigation-link-CVy0_kVl.js → navigation-link-mpHFXwRr.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-Dhb4KuMH.js +1 -0
  105. package/dist/assets/provider-scoped-model-input-CFsfWEt9.js +1 -0
  106. package/dist/assets/providers-config-page-D-TYFZsF.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-BlXwvOLo.js +8 -0
  115. package/dist/assets/{refresh-cw-j2bl79Ea.js → refresh-cw-JFuyDj_P.js} +1 -1
  116. package/dist/assets/remote-BwVv_Qq-.js +1 -0
  117. package/dist/assets/{requirementDiagram-TGXJPOKE-CyVUzEX5.js → requirementDiagram-TGXJPOKE-BiXNX1NN.js} +1 -1
  118. package/dist/assets/rotate-cw-DjbbiIW_.js +1 -0
  119. package/dist/assets/runtime-config-page-Br9j9Vxe.js +1 -0
  120. package/dist/assets/{sankeyDiagram-HTMAVEWB-JK6bFmYB.js → sankeyDiagram-HTMAVEWB-qzDeLztW.js} +1 -1
  121. package/dist/assets/{save-BKNrtK81.js → save-BL8P8CLX.js} +1 -1
  122. package/dist/assets/{search-CkOm65W_.js → search-bZL75Pgc.js} +1 -1
  123. package/dist/assets/search-config-page-B3fvzMd1.js +1 -0
  124. package/dist/assets/secrets-config-page-ClwTxtXd.js +3 -0
  125. package/dist/assets/security-config-DmJw9QZA.js +1 -0
  126. package/dist/assets/{select-Ea7ffKCE.js → select-rUZ8nIZu.js} +2 -2
  127. package/dist/assets/{sequenceDiagram-DBY2YBRQ-BR5IokXV.js → sequenceDiagram-DBY2YBRQ-BtRbPQtL.js} +1 -1
  128. package/dist/assets/{settings-2-CtyN3m_V.js → settings-2-BI4SCkxQ.js} +1 -1
  129. package/dist/assets/{sizeCapture-X5ZJPWSS-SxG_YnMA.js → sizeCapture-X5ZJPWSS-CMYvM3SL.js} +1 -1
  130. package/dist/assets/skeleton-CVmEPG43.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-C9l4xsKp.js → tag-chip-BdI_yIOA.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-BcEayhV6.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 +6 -6
  147. package/src/app/components/layout/app-layout.tsx +38 -2
  148. package/src/app/hooks/__tests__/use-app-event-consumers.test.ts +20 -0
  149. package/src/app/hooks/use-app-event-consumers.ts +21 -0
  150. package/src/app/presenters/app.presenter.ts +6 -0
  151. package/src/features/apps/components/__tests__/app-packages-panel.test.tsx +312 -0
  152. package/src/features/apps/components/__tests__/apps-panel.test.tsx +58 -0
  153. package/src/features/apps/components/app-artwork.tsx +49 -0
  154. package/src/features/apps/components/app-marketplace-catalog.tsx +332 -0
  155. package/src/features/apps/components/app-marketplace-cover.tsx +64 -0
  156. package/src/features/apps/components/app-marketplace-detail.tsx +161 -0
  157. package/src/features/apps/components/app-marketplace-dialog.tsx +152 -0
  158. package/src/features/apps/components/app-marketplace-operation.tsx +84 -0
  159. package/src/features/apps/components/app-package-card.tsx +417 -0
  160. package/src/features/apps/components/app-packages-panel.tsx +373 -0
  161. package/src/features/apps/components/apps-panel.tsx +31 -23
  162. package/src/features/apps/hooks/use-app-marketplace.test.ts +144 -0
  163. package/src/features/apps/hooks/use-app-marketplace.ts +65 -0
  164. package/src/features/apps/hooks/use-app-packages.ts +114 -0
  165. package/src/features/apps/index.ts +1 -0
  166. package/src/features/apps/managers/__tests__/app-package-operation-settlement.manager.test.ts +81 -0
  167. package/src/features/apps/managers/app-package-operation-settlement.manager.ts +80 -0
  168. package/src/features/apps/services/app-marketplace.service.ts +223 -0
  169. package/src/features/apps/types/app-marketplace.types.ts +75 -0
  170. package/src/features/chat/components/chat-sidebar-list-mode-switch.tsx +27 -4
  171. package/src/features/chat/components/layout/__tests__/chat-sidebar-session-area.test.tsx +36 -5
  172. package/src/features/chat/components/layout/chat-sidebar-desktop-layout.tsx +12 -13
  173. package/src/features/chat/features/conversation/components/__tests__/session-conversation-area.test.tsx +49 -1
  174. package/src/features/chat/features/conversation/components/__tests__/session-conversation-input.streaming.test.tsx +68 -7
  175. package/src/features/chat/features/conversation/components/session-conversation-area.tsx +25 -4
  176. package/src/features/chat/features/conversation/components/session-conversation-input.tsx +3 -0
  177. package/src/features/chat/features/conversation/components/session-queued-input-rows.tsx +24 -14
  178. package/src/features/chat/features/conversation/hooks/__tests__/use-session-conversation-controller.test.tsx +53 -0
  179. package/src/features/chat/features/conversation/hooks/__tests__/use-session-run-queue.test.tsx +18 -0
  180. package/src/features/chat/features/conversation/hooks/use-chat-composer-reference-intent.ts +4 -1
  181. package/src/features/chat/features/conversation/hooks/use-session-conversation-controller.ts +126 -32
  182. package/src/features/chat/features/conversation/hooks/use-session-run-queue.ts +12 -2
  183. package/src/features/chat/features/conversation/hooks/use-system-object-reference-select.ts +27 -0
  184. package/src/features/chat/features/conversation/utils/session-message-composer.utils.ts +5 -0
  185. package/src/features/chat/features/input/hooks/__tests__/use-chat-input-surface-state.test.tsx +102 -0
  186. package/src/features/chat/features/input/hooks/use-chat-input-surface-state.ts +71 -120
  187. package/src/features/chat/features/input/input-surface-plugins/__tests__/context-reference-plugin.test.ts +265 -18
  188. package/src/features/chat/features/input/input-surface-plugins/chat-input-product-plugin-adapters.types.ts +7 -2
  189. package/src/features/chat/features/input/input-surface-plugins/chat-input-product-plugin-adapters.utils.ts +124 -0
  190. package/src/features/chat/features/input/input-surface-plugins/context-reference-items.utils.ts +99 -15
  191. package/src/features/chat/features/input/input-surface-plugins/context-reference-plugin.utils.ts +143 -139
  192. package/src/features/chat/features/input/input-surface-plugins/panel-app-input-surface-items.utils.ts +116 -0
  193. package/src/features/chat/features/input/input-surface-plugins/slash-command-plugin.utils.ts +1 -1
  194. package/src/features/chat/features/input/utils/__tests__/chat-inline-token.utils.test.ts +84 -1
  195. package/src/features/chat/features/input/utils/chat-composer-token-protocol.utils.ts +10 -0
  196. package/src/features/chat/features/input/utils/chat-inline-token-composer.utils.ts +148 -0
  197. package/src/features/chat/features/input/utils/chat-inline-token.utils.ts +55 -101
  198. package/src/features/chat/features/input/utils/chat-system-object-reference.utils.test.ts +44 -0
  199. package/src/features/chat/features/input/utils/chat-system-object-reference.utils.ts +46 -0
  200. package/src/features/chat/features/message/hooks/use-chat-inline-token-actions.ts +8 -1
  201. package/src/features/chat/features/workspace/components/__tests__/chat-session-workspace-panel-content.test.tsx +155 -18
  202. package/src/features/chat/features/workspace/components/__tests__/chat-session-workspace-text-selection.test.tsx +12 -8
  203. package/src/features/chat/features/workspace/components/chat-session-workspace-directory-browser.tsx +80 -62
  204. package/src/features/chat/features/workspace/components/chat-session-workspace-panel-content.tsx +9 -2
  205. package/src/features/chat/features/workspace/components/project-files/workspace-directory-tree.tsx +83 -13
  206. package/src/features/chat/features/workspace/components/project-files/workspace-project-tree-view.tsx +93 -0
  207. package/src/features/chat/features/workspace/hooks/use-workspace-explorer-layout.ts +8 -11
  208. package/src/features/chat/features/workspace/hooks/use-workspace-file-actions.ts +68 -46
  209. package/src/features/chat/features/workspace/stores/__tests__/workspace-project-tree.store.test.ts +38 -0
  210. package/src/features/chat/features/workspace/stores/workspace-project-tree.store.ts +160 -0
  211. package/src/features/chat/index.ts +2 -1
  212. package/src/features/chat/managers/__tests__/chat-composer-intent.manager.test.ts +29 -0
  213. package/src/features/chat/managers/chat-composer-intent.manager.ts +29 -2
  214. package/src/features/chat/managers/chat-draft-intent.manager.ts +18 -2
  215. package/src/features/chat/presenters/chat.presenter.ts +2 -2
  216. package/src/features/chat/stores/__tests__/chat-thread.store.test.ts +6 -0
  217. package/src/features/chat/stores/chat-thread.store.ts +6 -0
  218. package/src/features/inbox/components/inbox-reader-dialog.test.tsx +38 -5
  219. package/src/features/inbox/components/inbox-reader-dialog.tsx +5 -3
  220. package/src/features/inbox/managers/inbox.manager.test.ts +28 -3
  221. package/src/features/inbox/managers/inbox.manager.ts +14 -4
  222. package/src/features/inbox/pages/inbox-page.test.ts +121 -2
  223. package/src/features/inbox/pages/inbox-page.tsx +5 -3
  224. package/src/features/marketplace/components/__tests__/marketplace-page.test.tsx +85 -1
  225. package/src/features/marketplace/hooks/__tests__/use-marketplace.test.tsx +44 -1
  226. package/src/features/marketplace/hooks/use-marketplace-item-actions.ts +23 -2
  227. package/src/features/marketplace/hooks/use-marketplace.ts +10 -0
  228. package/src/features/marketplace/index.ts +4 -0
  229. package/src/features/panel-apps/components/__tests__/panel-apps-list.test.tsx +1 -1
  230. package/src/features/panel-apps/components/panel-apps-list.tsx +1 -6
  231. package/src/features/panel-apps/index.ts +5 -1
  232. package/src/features/panel-apps/utils/__tests__/panel-app-doc-browser.utils.test.ts +13 -6
  233. package/src/features/panel-apps/utils/panel-app-doc-browser.utils.tsx +6 -3
  234. package/src/features/right-panel-resources/index.ts +1 -0
  235. package/src/features/right-panel-resources/utils/__tests__/right-panel-resource-uri.utils.test.ts +34 -0
  236. package/src/features/right-panel-resources/utils/right-panel-resource-uri.utils.ts +36 -4
  237. package/src/features/service-apps/components/service-apps-panel.tsx +3 -17
  238. package/src/features/system-status/components/__tests__/desktop-update-config.test.tsx +20 -1
  239. package/src/features/system-status/components/desktop-update-config.tsx +14 -13
  240. package/src/features/system-status/managers/__tests__/runtime-update.manager.test.ts +99 -0
  241. package/src/features/system-status/managers/runtime-update.manager.ts +22 -0
  242. package/src/features/system-status/stores/runtime-update.store.ts +1 -0
  243. package/src/platforms/desktop/components/desktop-app-shell.tsx +5 -0
  244. package/src/platforms/mobile/components/mobile-app-shell.tsx +9 -1
  245. package/src/shared/components/actions/__tests__/navigation-link.test.tsx +12 -0
  246. package/src/shared/components/actions/navigation-link.tsx +6 -3
  247. package/src/shared/components/doc-browser/__tests__/doc-browser.test.tsx +34 -0
  248. package/src/shared/components/doc-browser/doc-browser-tab-strip.tsx +5 -0
  249. package/src/shared/components/doc-browser/doc-browser.tsx +11 -1
  250. package/src/shared/components/doc-browser/managers/doc-browser.manager.ts +13 -0
  251. package/src/shared/components/ui/actions/__tests__/icon-action-button.test.tsx +15 -0
  252. package/src/shared/components/ui/actions/icon-action-button.tsx +4 -2
  253. package/src/shared/components/ui/tabs.tsx +24 -1
  254. package/src/shared/hooks/use-server-path-watch.ts +42 -0
  255. package/src/shared/hooks/use-system-object-references.ts +22 -0
  256. package/src/shared/lib/api/server-path/server-path.types.ts +10 -0
  257. package/src/shared/lib/api/types.ts +13 -0
  258. package/src/shared/lib/api/utils/server-path.utils.ts +10 -0
  259. package/src/shared/lib/i18n/locales/en-US/chat.json +17 -9
  260. package/src/shared/lib/i18n/locales/en-US/desktop-update.json +7 -4
  261. package/src/shared/lib/i18n/locales/en-US/doc-browser.json +82 -0
  262. package/src/shared/lib/i18n/locales/en-US/marketplace.json +3 -0
  263. package/src/shared/lib/i18n/locales/zh-CN/chat.json +19 -11
  264. package/src/shared/lib/i18n/locales/zh-CN/desktop-update.json +7 -4
  265. package/src/shared/lib/i18n/locales/zh-CN/doc-browser.json +82 -0
  266. package/src/shared/lib/i18n/locales/zh-CN/marketplace.json +3 -0
  267. package/vite.config.ts +32 -1
  268. package/dist/assets/appearance-settings-page-BYbvl2wj.js +0 -1
  269. package/dist/assets/architecture-TIHT7OUA-Bjp9jf0h.js +0 -1
  270. package/dist/assets/channel-5T3pdR-s.js +0 -1
  271. package/dist/assets/chat-page-C5E-LLVd.js +0 -1
  272. package/dist/assets/chunk-2Q5K7J3B-DTZ3tGfE.js +0 -1
  273. package/dist/assets/chunk-VAUOI2AC-HLGIHQ6k.js +0 -1
  274. package/dist/assets/chunk-XXDRQBXY-Dl0m6Ni-.js +0 -1
  275. package/dist/assets/classDiagram-OUVF2IWQ-DcDnzGNX.js +0 -1
  276. package/dist/assets/classDiagram-v2-EOCWNBFH-BKXKMzKM.js +0 -1
  277. package/dist/assets/config-split-page-CA_a0AJ5.js +0 -1
  278. package/dist/assets/confirm-dialog-CxZfenjK.js +0 -5
  279. package/dist/assets/cynefin-VYW2F7L2-BWg7U2UD.js +0 -1
  280. package/dist/assets/desktop-update-config-BEKC8Ly9.js +0 -1
  281. package/dist/assets/doc-browser-Cl2ldE3f.js +0 -1
  282. package/dist/assets/doc-browser-DXCSd63E.js +0 -1
  283. package/dist/assets/doc-browser-context-FDPs3gJf.js +0 -1
  284. package/dist/assets/eventmodeling-45OFAUF4-SB-J4tpq.js +0 -1
  285. package/dist/assets/flowDiagram-23GEKE2U-BQ_u-eLM.js +0 -1
  286. package/dist/assets/gitGraph-TEB2WS4Q-DPxNwsFd.js +0 -1
  287. package/dist/assets/i18n-provider-DkITJNz8.js +0 -1
  288. package/dist/assets/index-CQ2LcI2p.css +0 -1
  289. package/dist/assets/index-CrdfvBVg.js +0 -107
  290. package/dist/assets/info-DKCQHKI2-DNlNGH90.js +0 -1
  291. package/dist/assets/loader-circle-DHtUxCIA.js +0 -1
  292. package/dist/assets/mcp-marketplace-page-Bc_jI5iH.js +0 -9
  293. package/dist/assets/mcp-marketplace-page-CzCieD9r.js +0 -1
  294. package/dist/assets/model-config-page-C4jw-pcj.js +0 -1
  295. package/dist/assets/packet-7NZHBO7P-BpeiqkBZ.js +0 -1
  296. package/dist/assets/pie-RZYD4A2V-Cy1yxsPd.js +0 -1
  297. package/dist/assets/plus-CG1RwZQq.js +0 -1
  298. package/dist/assets/provider-scoped-model-input-CmH4BFvc.js +0 -1
  299. package/dist/assets/providers-config-page-CJQjwzaw.js +0 -1
  300. package/dist/assets/radar-I7S5WNFK-CEcPKCj8.js +0 -1
  301. package/dist/assets/railroad-3IZDKUUU-CW38eNDs.js +0 -1
  302. package/dist/assets/railroad-abnf-AHOZXSZD-ByrylcXf.js +0 -1
  303. package/dist/assets/railroad-ebnf-EBAXGLYW-CNB6GwP-.js +0 -1
  304. package/dist/assets/railroad-peg-LSFZ7HO6-x6hnqlBG.js +0 -1
  305. package/dist/assets/react-DjFBVA0n.js +0 -8
  306. package/dist/assets/remote-BIlpD3yL.js +0 -1
  307. package/dist/assets/rotate-cw-bKG8zH8M.js +0 -1
  308. package/dist/assets/runtime-config-page-B_x2Glz5.js +0 -1
  309. package/dist/assets/search-config-page-BaIijtmO.js +0 -1
  310. package/dist/assets/secrets-config-page-CJjtdFAl.js +0 -3
  311. package/dist/assets/security-config-DB8apj09.js +0 -1
  312. package/dist/assets/skeleton-Bt6zxcAB.js +0 -1
  313. package/dist/assets/stateDiagram-v2-6OUMAXLB-ZC6dFORv.js +0 -1
  314. package/dist/assets/swimlanesDiagram-G3AALYLV-CRkh51Im.js +0 -8
  315. package/dist/assets/treeView-QDETBFTQ-Bw0DSCH7.js +0 -1
  316. package/dist/assets/treemap-6X3UGDF4-DFWBFcfh.js +0 -1
  317. package/dist/assets/wardley-OPB4EBWU-CoWturk0.js +0 -1
  318. package/dist/assets/x-CowtCu9Z.js +0 -1
  319. /package/dist/assets/{array-NADKd9aU.js → array-BQVzeGRt.js} +0 -0
  320. /package/dist/assets/{chunk-KEIR6QF5-BuyuzJJL.js → chunk-KEIR6QF5-f9psyCXK.js} +0 -0
  321. /package/dist/assets/{chunk-Y2CYZVJY-D_B2ZRNt.js → chunk-Y2CYZVJY-D1mAU_Po.js} +0 -0
  322. /package/dist/assets/{config-hints-BcfVUMij.js → config-hints-nV5HPOvq.js} +0 -0
  323. /package/dist/assets/{cytoscape.esm-BANTlgza.js → cytoscape.esm-0uDtdD0l.js} +0 -0
  324. /package/dist/assets/{defaultLocale-C4_L0aEs.js → defaultLocale-DSWXGgt0.js} +0 -0
  325. /package/dist/assets/{dist-DAPEIJbz.js → dist--ta5rX12.js} +0 -0
  326. /package/dist/assets/{graphlib-ChqXg0dI.js → graphlib-CQ6QV4yP.js} +0 -0
  327. /package/dist/assets/{host-capabilities-D9C4CBK5.js → host-capabilities-Tk038i04.js} +0 -0
  328. /package/dist/assets/{init-D8wZg9xq.js → init-Y6h9jGtZ.js} +0 -0
  329. /package/dist/assets/{jszip.min-CkbBfwAQ.js → jszip.min-8hyJpgpx.js} +0 -0
  330. /package/dist/assets/{katex-TZJFa0J_.js → katex-ARhY9PS1.js} +0 -0
  331. /package/dist/assets/{middleware-B3k-TSmZ.js → middleware-DXsbf9xI.js} +0 -0
  332. /package/dist/assets/{path-DodfxcX7.js → path-10BjdyLh.js} +0 -0
  333. /package/dist/assets/{rough.esm-CUaa1Rbe.js → rough.esm-DxgC_14r.js} +0 -0
  334. /package/dist/assets/{xlsx-CxByi7bR.js → xlsx-DrDuDNAa.js} +0 -0
@@ -1,4 +1,4 @@
1
- import { useState, type ReactNode } from "react";
1
+ import { useState } from "react";
2
2
  import type {
3
3
  ServiceActionGrantView,
4
4
  ServiceActionListView,
@@ -25,11 +25,7 @@ import { t } from "@/shared/lib/i18n";
25
25
 
26
26
  type ServiceActionView = ServiceActionListView["actions"][number];
27
27
 
28
- export function ServiceAppsPanel({
29
- headerContent,
30
- }: {
31
- headerContent?: ReactNode;
32
- }) {
28
+ export function ServiceAppsPanel() {
33
29
  const serviceApps = useServiceApps();
34
30
  const serviceActions = useServiceActions();
35
31
  const serviceActionGrants = useServiceActionGrants();
@@ -92,17 +88,7 @@ export function ServiceAppsPanel({
92
88
 
93
89
  return (
94
90
  <div className="flex h-full min-h-0 flex-col bg-card text-card-foreground">
95
- <div className="flex items-center justify-between gap-2 border-b border-border/60 px-4 py-3">
96
- <div className="flex min-w-0 items-center gap-2">
97
- {headerContent ?? (
98
- <>
99
- <Server className="h-4 w-4 text-primary" />
100
- <div className="truncate text-sm font-semibold text-foreground">
101
- {t("serviceAppsTitle")}
102
- </div>
103
- </>
104
- )}
105
- </div>
91
+ <div className="flex min-h-12 shrink-0 items-center justify-end border-b border-border/60 px-4 py-2">
106
92
  <TooltipProvider delayDuration={250}>
107
93
  <Tooltip>
108
94
  <TooltipTrigger asChild>
@@ -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
 
@@ -10,6 +10,7 @@ import { isWindowsDesktopHost } from "@/platforms/desktop/utils/desktop-host.uti
10
10
  import { MobileBottomNav } from "@/platforms/mobile";
11
11
  import type { DocBrowserCustomTabRenderers } from "@/shared/components/doc-browser/doc-browser-renderer.types";
12
12
  import type { DocBrowserDockControls } from "@/shared/components/doc-browser/doc-browser-context";
13
+ import type { DocBrowserTabMenuGroupsResolver } from "@/shared/components/doc-browser/doc-browser";
13
14
  import { cn } from "@/shared/lib/utils";
14
15
  import { useViewportLayoutStore } from "@/app/stores/viewport-layout.store";
15
16
  import { SIDEBAR_RAIL_WIDTH_PX } from "@/app/components/layout/sidebar-rail.styles";
@@ -26,6 +27,7 @@ type DesktopAppShellProps = {
26
27
  docBrowserMode: "floating" | "docked";
27
28
  docBrowserDockControls?: DocBrowserDockControls;
28
29
  docBrowserRenderers?: DocBrowserCustomTabRenderers;
30
+ docBrowserTabMenuGroups?: DocBrowserTabMenuGroupsResolver;
29
31
  sideDock?: React.ReactNode;
30
32
  children: React.ReactNode;
31
33
  };
@@ -37,6 +39,7 @@ export function DesktopAppShell({
37
39
  docBrowserMode,
38
40
  docBrowserDockControls,
39
41
  docBrowserRenderers = {},
42
+ docBrowserTabMenuGroups,
40
43
  sideDock,
41
44
  children,
42
45
  }: DesktopAppShellProps) {
@@ -91,6 +94,7 @@ export function DesktopAppShell({
91
94
  <DocBrowser
92
95
  customTabRenderers={docBrowserRenderers}
93
96
  dockControls={docBrowserDockControls}
97
+ getTabMenuGroups={docBrowserTabMenuGroups}
94
98
  />
95
99
  </Suspense>
96
100
  ) : null}
@@ -103,6 +107,7 @@ export function DesktopAppShell({
103
107
  <DocBrowser
104
108
  customTabRenderers={docBrowserRenderers}
105
109
  dockControls={docBrowserDockControls}
110
+ getTabMenuGroups={docBrowserTabMenuGroups}
106
111
  />
107
112
  </Suspense>
108
113
  ) : null}
@@ -7,6 +7,7 @@ import { MobileBottomNav } from "@/platforms/mobile/components/mobile-bottom-nav
7
7
  import { MobileTopbar } from "@/platforms/mobile/components/mobile-topbar";
8
8
  import type { DocBrowserCustomTabRenderers } from "@/shared/components/doc-browser/doc-browser-renderer.types";
9
9
  import type { DocBrowserDockControls } from "@/shared/components/doc-browser/doc-browser-context";
10
+ import type { DocBrowserTabMenuGroupsResolver } from "@/shared/components/doc-browser/doc-browser";
10
11
 
11
12
  const DocBrowser = lazy(async () => ({
12
13
  default: (await import("@/shared/components/doc-browser/doc-browser")).DocBrowser,
@@ -17,6 +18,7 @@ type MobileAppShellProps = {
17
18
  isDocBrowserOpen: boolean;
18
19
  docBrowserDockControls?: DocBrowserDockControls;
19
20
  docBrowserRenderers?: DocBrowserCustomTabRenderers;
21
+ docBrowserTabMenuGroups?: DocBrowserTabMenuGroupsResolver;
20
22
  topbarLeadingInset?: string;
21
23
  children: React.ReactNode;
22
24
  };
@@ -26,6 +28,7 @@ export function MobileAppShell({
26
28
  isDocBrowserOpen,
27
29
  docBrowserDockControls,
28
30
  docBrowserRenderers = {},
31
+ docBrowserTabMenuGroups,
29
32
  topbarLeadingInset,
30
33
  children,
31
34
  }: MobileAppShellProps) {
@@ -48,7 +51,12 @@ export function MobileAppShell({
48
51
  {showBottomNav ? <MobileBottomNav /> : null}
49
52
  {isDocBrowserOpen ? (
50
53
  <Suspense fallback={null}>
51
- <DocBrowser customTabRenderers={docBrowserRenderers} displayMode="fullscreen" dockControls={docBrowserDockControls} />
54
+ <DocBrowser
55
+ customTabRenderers={docBrowserRenderers}
56
+ displayMode="fullscreen"
57
+ dockControls={docBrowserDockControls}
58
+ getTabMenuGroups={docBrowserTabMenuGroups}
59
+ />
52
60
  </Suspense>
53
61
  ) : null}
54
62
  </div>
@@ -1,3 +1,4 @@
1
+ import { createRef } from "react";
1
2
  import { fireEvent, render, screen } from "@testing-library/react";
2
3
  import { MemoryRouter, Route, Routes } from "react-router-dom";
3
4
  import { beforeEach, describe, expect, it, vi } from "vitest";
@@ -55,4 +56,15 @@ describe("NavigationLink", () => {
55
56
  expect(screen.getByText("Session route")).toBeTruthy();
56
57
  expect(mocks.openExternalUrl).not.toHaveBeenCalled();
57
58
  });
59
+
60
+ it("forwards refs to the rendered anchor for asChild consumers", () => {
61
+ const ref = createRef<HTMLAnchorElement>();
62
+ render(
63
+ <NavigationLink ref={ref} href="https://docs.nextclaw.io/" external>
64
+ View docs
65
+ </NavigationLink>,
66
+ );
67
+
68
+ expect(ref.current).toBe(screen.getByRole("link", { name: "View docs" }));
69
+ });
58
70
  });
@@ -3,6 +3,7 @@ import type {
3
3
  MouseEvent,
4
4
  ReactNode,
5
5
  } from "react";
6
+ import { forwardRef } from "react";
6
7
  import { ExternalLink as ExternalLinkIcon, type LucideIcon } from "lucide-react";
7
8
  import { Link } from "react-router-dom";
8
9
  import { hostCapabilityManager } from "@/shared/lib/host-capabilities";
@@ -62,7 +63,7 @@ function resolveExternalHttpUrl(href: string): string | null {
62
63
  * Internal links use client-side routing; external links delegate ordinary
63
64
  * clicks to the desktop host when one is available.
64
65
  */
65
- export function NavigationLink({
66
+ export const NavigationLink = forwardRef<HTMLAnchorElement, NavigationLinkProps>(function NavigationLink({
66
67
  children,
67
68
  className,
68
69
  external = false,
@@ -74,7 +75,7 @@ export function NavigationLink({
74
75
  size = "sm",
75
76
  target,
76
77
  ...props
77
- }: NavigationLinkProps) {
78
+ }, ref) {
78
79
  const Icon = icon === undefined && external ? ExternalLinkIcon : icon;
79
80
  const linkClassName = cn(
80
81
  "inline-flex items-center rounded-sm font-medium text-primary underline-offset-4 transition-colors hover:text-primary-hover hover:underline focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/25",
@@ -98,6 +99,7 @@ export function NavigationLink({
98
99
  if (!external) {
99
100
  return (
100
101
  <Link
102
+ ref={ref}
101
103
  {...props}
102
104
  to={href}
103
105
  target={target}
@@ -112,6 +114,7 @@ export function NavigationLink({
112
114
 
113
115
  return (
114
116
  <a
117
+ ref={ref}
115
118
  {...props}
116
119
  href={href}
117
120
  target={target ?? "_blank"}
@@ -133,4 +136,4 @@ export function NavigationLink({
133
136
  {content}
134
137
  </a>
135
138
  );
136
- }
139
+ });
@@ -571,6 +571,40 @@ describe("DocBrowser floating interactions", () => {
571
571
  expect(onDragStart).toHaveBeenCalledTimes(1);
572
572
  });
573
573
 
574
+ it("opens the same tab action from right click and the more button", () => {
575
+ const onAddToChat = vi.fn();
576
+
577
+ render(
578
+ <DocBrowserTabStrip
579
+ tabs={docBrowserState.tabs}
580
+ activeTabId="docs"
581
+ canGoBack={false}
582
+ canGoForward={false}
583
+ isDocked={true}
584
+ isFullscreen={false}
585
+ onGoBack={vi.fn()}
586
+ onGoForward={vi.fn()}
587
+ onOpenNewTab={vi.fn()}
588
+ onSetActiveTab={vi.fn()}
589
+ onCloseTab={vi.fn()}
590
+ onClose={vi.fn()}
591
+ onDragStart={vi.fn()}
592
+ onToggleMode={vi.fn()}
593
+ getTabMenuGroups={() => [{
594
+ key: "chat",
595
+ items: [{ key: "add", label: "Add to Chat", onSelect: onAddToChat }],
596
+ }]}
597
+ />,
598
+ );
599
+
600
+ fireEvent.contextMenu(screen.getByRole("button", { name: "Docs" }));
601
+ fireEvent.click(screen.getByRole("menuitem", { name: "Add to Chat" }));
602
+ fireEvent.click(screen.getByRole("button", { name: "More tab actions" }));
603
+ fireEvent.click(screen.getByRole("menuitem", { name: "Add to Chat" }));
604
+
605
+ expect(onAddToChat).toHaveBeenCalledTimes(2);
606
+ });
607
+
574
608
  it("keeps the floating panel left edge stable when resizing from the right", () => {
575
609
  docBrowserState.mode = "floating";
576
610
 
@@ -10,6 +10,7 @@ import {
10
10
  X,
11
11
  } from "lucide-react";
12
12
  import type { DocBrowserDockState, DocBrowserTab } from "./doc-browser-context";
13
+ import type { ContextMenuGroup } from "@/shared/components/ui/context-menu/context-menu";
13
14
  import {
14
15
  CompactTabStrip,
15
16
  type CompactTabStripAction,
@@ -35,6 +36,7 @@ type DocBrowserTabStripProps = {
35
36
  onClose: () => void;
36
37
  onDragStart: (event: PointerEvent<HTMLElement>) => void;
37
38
  onToggleMode: () => void;
39
+ getTabMenuGroups?: (tab: DocBrowserTab) => readonly ContextMenuGroup[] | undefined;
38
40
  };
39
41
 
40
42
  function shouldBlockHeaderDrag(target: EventTarget | null): boolean {
@@ -60,6 +62,7 @@ export function DocBrowserTabStrip({
60
62
  onClose,
61
63
  onDragStart,
62
64
  onToggleMode,
65
+ getTabMenuGroups,
63
66
  }: DocBrowserTabStripProps) {
64
67
  const backLabel = t("docBrowserBack");
65
68
  const closeLabel = t("docBrowserClose");
@@ -81,6 +84,8 @@ export function DocBrowserTabStrip({
81
84
  closePlacement: "trailing",
82
85
  onSelect: () => onSetActiveTab(tab.id),
83
86
  onClose: () => onCloseTab(tab.id),
87
+ menuLabel: t("docBrowserTabMoreActions"),
88
+ menuGroups: getTabMenuGroups?.(tab),
84
89
  }));
85
90
  const actions: CompactTabStripAction[] = [
86
91
  { key: "back", disabled: !canGoBack, icon: <ArrowLeft className="h-3.5 w-3.5" />, label: backLabel, onClick: onGoBack },
@@ -28,6 +28,7 @@ import {
28
28
  import { ResizableRightPanel } from '@/shared/components/resizable-right-panel/resizable-right-panel';
29
29
  import { cn } from '@/shared/lib/utils';
30
30
  import { t } from '@/shared/lib/i18n';
31
+ import type { ContextMenuGroup } from '@/shared/components/ui/context-menu/context-menu';
31
32
  import { GripVertical } from 'lucide-react';
32
33
  import {
33
34
  DOC_BROWSER_DOCKED_MAX_WIDTH,
@@ -38,8 +39,11 @@ type DocBrowserProps = {
38
39
  customTabRenderers?: DocBrowserCustomTabRenderers;
39
40
  displayMode?: 'desktop' | 'fullscreen';
40
41
  dockControls?: DocBrowserDockControls;
42
+ getTabMenuGroups?: (tab: DocBrowserTab) => readonly ContextMenuGroup[] | undefined;
41
43
  };
42
44
 
45
+ export type DocBrowserTabMenuGroupsResolver = NonNullable<DocBrowserProps['getTabMenuGroups']>;
46
+
43
47
  type FloatingPanelInteraction = {
44
48
  startX: number;
45
49
  startY: number;
@@ -160,7 +164,12 @@ function useDocBrowserAddressBar({
160
164
  };
161
165
  }
162
166
 
163
- export function DocBrowser({ customTabRenderers = {}, displayMode = 'desktop', dockControls }: DocBrowserProps) {
167
+ export function DocBrowser({
168
+ customTabRenderers = {},
169
+ displayMode = 'desktop',
170
+ dockControls,
171
+ getTabMenuGroups,
172
+ }: DocBrowserProps) {
164
173
  const {
165
174
  isOpen,
166
175
  mode,
@@ -367,6 +376,7 @@ export function DocBrowser({ customTabRenderers = {}, displayMode = 'desktop', d
367
376
  onClose={close}
368
377
  onDragStart={startFloatDrag}
369
378
  onToggleMode={toggleMode}
379
+ getTabMenuGroups={getTabMenuGroups}
370
380
  />
371
381
 
372
382
  <DocBrowserAddressToolbar
@@ -267,6 +267,19 @@ export class DocBrowserManager {
267
267
  this.onRightPanelOpened?.();
268
268
  };
269
269
 
270
+ readonly reloadByDedupeKeys = (dedupeKeys: readonly string[]): void => {
271
+ const targets = new Set(dedupeKeys);
272
+ if (targets.size === 0) {
273
+ return;
274
+ }
275
+ this.setSnapshot((prev) => ({
276
+ ...prev,
277
+ tabs: prev.tabs.map((tab) => targets.has(tab.dedupeKey ?? "")
278
+ ? { ...tab, navVersion: tab.navVersion + 1 }
279
+ : tab),
280
+ }));
281
+ };
282
+
270
283
  readonly openNewTab = (): void => {
271
284
  this.open(undefined, { kind: DOC_BROWSER_HOME_TAB_KIND, newTab: true });
272
285
  };
@@ -34,6 +34,21 @@ describe('IconActionButton', () => {
34
34
  expect(button.className).toContain('hover:bg-[var(--interaction-hover)]');
35
35
  });
36
36
 
37
+ it('uses surface-relative feedback without introducing a new surface', () => {
38
+ render(
39
+ <IconActionButton
40
+ tone="surface"
41
+ icon={<span data-testid="icon" />}
42
+ label="Add project"
43
+ />,
44
+ );
45
+
46
+ const button = screen.getByRole('button', { name: 'Add project' });
47
+ expect(button.className).toContain('hover:bg-gray-200/60');
48
+ expect(button.className).toContain('hover:text-gray-900');
49
+ expect(button.className).toContain('active:bg-gray-200/80');
50
+ });
51
+
37
52
  it('ignores restored pointer focus while preserving keyboard tooltip focus', () => {
38
53
  render(
39
54
  <IconActionButton
@@ -8,7 +8,7 @@ import {
8
8
  import { cn } from '@/shared/lib/utils';
9
9
 
10
10
  type IconActionButtonSize = 'sm' | 'md' | 'lg';
11
- type IconActionButtonTone = 'default' | 'strong';
11
+ type IconActionButtonTone = 'default' | 'surface' | 'strong';
12
12
 
13
13
  type IconActionButtonProps = Omit<
14
14
  React.ButtonHTMLAttributes<HTMLButtonElement>,
@@ -17,7 +17,7 @@ type IconActionButtonProps = Omit<
17
17
  icon: React.ReactNode;
18
18
  label: string;
19
19
  size?: IconActionButtonSize;
20
- /** default: soft accent hover. strong: denser gray hover for nested hover surfaces. */
20
+ /** default: global interaction hover. surface: relative feedback on the current surface. strong: denser feedback for nested hover surfaces. */
21
21
  tone?: IconActionButtonTone;
22
22
  tooltip?: string | false | null;
23
23
  tooltipSide?: 'top' | 'right' | 'bottom' | 'left';
@@ -32,6 +32,8 @@ const SIZE_CLASS: Record<IconActionButtonSize, string> = {
32
32
  const TONE_CLASS: Record<IconActionButtonTone, string> = {
33
33
  default:
34
34
  'text-muted-foreground hover:bg-[var(--interaction-hover)] hover:text-accent-foreground disabled:text-muted-foreground/45 disabled:hover:bg-transparent disabled:hover:text-muted-foreground/45',
35
+ surface:
36
+ 'text-muted-foreground hover:bg-gray-200/60 hover:text-gray-900 active:bg-gray-200/80 disabled:text-muted-foreground/45 disabled:hover:bg-transparent disabled:hover:text-muted-foreground/45',
35
37
  strong:
36
38
  'text-muted-foreground hover:bg-black/10 hover:text-foreground disabled:text-muted-foreground/45 disabled:hover:bg-transparent disabled:hover:text-muted-foreground/45',
37
39
  };