@nextclaw/ui 0.15.25 → 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 (288) hide show
  1. package/CHANGELOG.md +18 -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-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-CocdcIko.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-BH7W5yCi.js → dist-AYxkFx2B.js} +1 -1
  68. package/dist/assets/{dist-CaYdVVeO.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-CVy0_kVl.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-j2bl79Ea.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-BKNrtK81.js → save-CGm23Yww.js} +1 -1
  122. package/dist/assets/{search-CkOm65W_.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-CJjtdFAl.js → secrets-config-page-BlPOuql_.js} +2 -2
  125. package/dist/assets/security-config-CbDiY2xr.js +1 -0
  126. package/dist/assets/{select-Ea7ffKCE.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-CtyN3m_V.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-C9l4xsKp.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 +6 -6
  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 +49 -1
  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 +25 -4
  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/workspace/components/__tests__/chat-session-workspace-panel-content.test.tsx +82 -2
  179. package/src/features/chat/features/workspace/components/__tests__/chat-session-workspace-text-selection.test.tsx +12 -8
  180. package/src/features/chat/features/workspace/components/chat-session-workspace-directory-browser.tsx +24 -12
  181. package/src/features/chat/features/workspace/components/chat-session-workspace-panel-content.tsx +9 -2
  182. package/src/features/chat/features/workspace/components/project-files/workspace-directory-tree.tsx +52 -4
  183. package/src/features/chat/features/workspace/hooks/use-workspace-explorer-layout.ts +8 -11
  184. package/src/features/chat/features/workspace/hooks/use-workspace-file-actions.ts +68 -46
  185. package/src/features/chat/index.ts +1 -1
  186. package/src/features/chat/managers/chat-draft-intent.manager.ts +18 -2
  187. package/src/features/chat/stores/__tests__/chat-thread.store.test.ts +6 -0
  188. package/src/features/chat/stores/chat-thread.store.ts +6 -0
  189. package/src/features/inbox/components/inbox-reader-dialog.test.tsx +38 -5
  190. package/src/features/inbox/components/inbox-reader-dialog.tsx +5 -3
  191. package/src/features/inbox/managers/inbox.manager.test.ts +28 -3
  192. package/src/features/inbox/managers/inbox.manager.ts +14 -4
  193. package/src/features/inbox/pages/inbox-page.test.ts +121 -2
  194. package/src/features/inbox/pages/inbox-page.tsx +5 -3
  195. package/src/features/marketplace/components/__tests__/marketplace-page.test.tsx +85 -1
  196. package/src/features/marketplace/hooks/__tests__/use-marketplace.test.tsx +44 -1
  197. package/src/features/marketplace/hooks/use-marketplace-item-actions.ts +23 -2
  198. package/src/features/marketplace/hooks/use-marketplace.ts +10 -0
  199. package/src/features/marketplace/index.ts +4 -0
  200. package/src/features/panel-apps/index.ts +5 -1
  201. package/src/features/panel-apps/utils/__tests__/panel-app-doc-browser.utils.test.ts +13 -6
  202. package/src/features/panel-apps/utils/panel-app-doc-browser.utils.tsx +6 -3
  203. package/src/features/right-panel-resources/index.ts +1 -0
  204. package/src/features/right-panel-resources/utils/right-panel-resource-uri.utils.ts +7 -4
  205. package/src/features/system-status/components/__tests__/desktop-update-config.test.tsx +20 -1
  206. package/src/features/system-status/components/desktop-update-config.tsx +14 -13
  207. package/src/features/system-status/managers/__tests__/runtime-update.manager.test.ts +99 -0
  208. package/src/features/system-status/managers/runtime-update.manager.ts +22 -0
  209. package/src/features/system-status/stores/runtime-update.store.ts +1 -0
  210. package/src/shared/components/ui/actions/__tests__/icon-action-button.test.tsx +15 -0
  211. package/src/shared/components/ui/actions/icon-action-button.tsx +4 -2
  212. package/src/shared/hooks/use-system-object-references.ts +22 -0
  213. package/src/shared/lib/api/types.ts +10 -0
  214. package/src/shared/lib/i18n/locales/en-US/chat.json +16 -9
  215. package/src/shared/lib/i18n/locales/en-US/desktop-update.json +7 -4
  216. package/src/shared/lib/i18n/locales/en-US/doc-browser.json +40 -0
  217. package/src/shared/lib/i18n/locales/en-US/marketplace.json +3 -0
  218. package/src/shared/lib/i18n/locales/zh-CN/chat.json +18 -11
  219. package/src/shared/lib/i18n/locales/zh-CN/desktop-update.json +7 -4
  220. package/src/shared/lib/i18n/locales/zh-CN/doc-browser.json +40 -0
  221. package/src/shared/lib/i18n/locales/zh-CN/marketplace.json +3 -0
  222. package/dist/assets/appearance-settings-page-BYbvl2wj.js +0 -1
  223. package/dist/assets/architecture-TIHT7OUA-Bjp9jf0h.js +0 -1
  224. package/dist/assets/channel-5T3pdR-s.js +0 -1
  225. package/dist/assets/chat-page-C5E-LLVd.js +0 -1
  226. package/dist/assets/chunk-2Q5K7J3B-DTZ3tGfE.js +0 -1
  227. package/dist/assets/chunk-VAUOI2AC-HLGIHQ6k.js +0 -1
  228. package/dist/assets/chunk-XXDRQBXY-Dl0m6Ni-.js +0 -1
  229. package/dist/assets/classDiagram-OUVF2IWQ-DcDnzGNX.js +0 -1
  230. package/dist/assets/classDiagram-v2-EOCWNBFH-BKXKMzKM.js +0 -1
  231. package/dist/assets/config-split-page-CA_a0AJ5.js +0 -1
  232. package/dist/assets/confirm-dialog-CxZfenjK.js +0 -5
  233. package/dist/assets/cynefin-VYW2F7L2-BWg7U2UD.js +0 -1
  234. package/dist/assets/desktop-update-config-BEKC8Ly9.js +0 -1
  235. package/dist/assets/doc-browser-Cl2ldE3f.js +0 -1
  236. package/dist/assets/doc-browser-DXCSd63E.js +0 -1
  237. package/dist/assets/doc-browser-context-FDPs3gJf.js +0 -1
  238. package/dist/assets/eventmodeling-45OFAUF4-SB-J4tpq.js +0 -1
  239. package/dist/assets/flowDiagram-23GEKE2U-BQ_u-eLM.js +0 -1
  240. package/dist/assets/form-actions-0c0aw_bl.js +0 -1
  241. package/dist/assets/gitGraph-TEB2WS4Q-DPxNwsFd.js +0 -1
  242. package/dist/assets/i18n-provider-DkITJNz8.js +0 -1
  243. package/dist/assets/index-CQ2LcI2p.css +0 -1
  244. package/dist/assets/index-CrdfvBVg.js +0 -107
  245. package/dist/assets/info-DKCQHKI2-DNlNGH90.js +0 -1
  246. package/dist/assets/loader-circle-DHtUxCIA.js +0 -1
  247. package/dist/assets/mcp-marketplace-page-Bc_jI5iH.js +0 -9
  248. package/dist/assets/mcp-marketplace-page-CzCieD9r.js +0 -1
  249. package/dist/assets/model-config-page-C4jw-pcj.js +0 -1
  250. package/dist/assets/packet-7NZHBO7P-BpeiqkBZ.js +0 -1
  251. package/dist/assets/pie-RZYD4A2V-Cy1yxsPd.js +0 -1
  252. package/dist/assets/plus-CG1RwZQq.js +0 -1
  253. package/dist/assets/provider-scoped-model-input-CmH4BFvc.js +0 -1
  254. package/dist/assets/providers-config-page-CJQjwzaw.js +0 -1
  255. package/dist/assets/radar-I7S5WNFK-CEcPKCj8.js +0 -1
  256. package/dist/assets/railroad-3IZDKUUU-CW38eNDs.js +0 -1
  257. package/dist/assets/railroad-abnf-AHOZXSZD-ByrylcXf.js +0 -1
  258. package/dist/assets/railroad-ebnf-EBAXGLYW-CNB6GwP-.js +0 -1
  259. package/dist/assets/railroad-peg-LSFZ7HO6-x6hnqlBG.js +0 -1
  260. package/dist/assets/react-DjFBVA0n.js +0 -8
  261. package/dist/assets/remote-BIlpD3yL.js +0 -1
  262. package/dist/assets/rotate-cw-bKG8zH8M.js +0 -1
  263. package/dist/assets/runtime-config-page-B_x2Glz5.js +0 -1
  264. package/dist/assets/search-config-page-BaIijtmO.js +0 -1
  265. package/dist/assets/security-config-DB8apj09.js +0 -1
  266. package/dist/assets/skeleton-Bt6zxcAB.js +0 -1
  267. package/dist/assets/stateDiagram-v2-6OUMAXLB-ZC6dFORv.js +0 -1
  268. package/dist/assets/swimlanesDiagram-G3AALYLV-CRkh51Im.js +0 -8
  269. package/dist/assets/treeView-QDETBFTQ-Bw0DSCH7.js +0 -1
  270. package/dist/assets/treemap-6X3UGDF4-DFWBFcfh.js +0 -1
  271. package/dist/assets/wardley-OPB4EBWU-CoWturk0.js +0 -1
  272. package/dist/assets/x-CowtCu9Z.js +0 -1
  273. /package/dist/assets/{array-NADKd9aU.js → array-BQVzeGRt.js} +0 -0
  274. /package/dist/assets/{chunk-KEIR6QF5-BuyuzJJL.js → chunk-KEIR6QF5-f9psyCXK.js} +0 -0
  275. /package/dist/assets/{chunk-Y2CYZVJY-D_B2ZRNt.js → chunk-Y2CYZVJY-D1mAU_Po.js} +0 -0
  276. /package/dist/assets/{config-hints-BcfVUMij.js → config-hints-nV5HPOvq.js} +0 -0
  277. /package/dist/assets/{cytoscape.esm-BANTlgza.js → cytoscape.esm-0uDtdD0l.js} +0 -0
  278. /package/dist/assets/{defaultLocale-C4_L0aEs.js → defaultLocale-DSWXGgt0.js} +0 -0
  279. /package/dist/assets/{dist-DAPEIJbz.js → dist--ta5rX12.js} +0 -0
  280. /package/dist/assets/{graphlib-ChqXg0dI.js → graphlib-CQ6QV4yP.js} +0 -0
  281. /package/dist/assets/{host-capabilities-D9C4CBK5.js → host-capabilities-Tk038i04.js} +0 -0
  282. /package/dist/assets/{init-D8wZg9xq.js → init-Y6h9jGtZ.js} +0 -0
  283. /package/dist/assets/{jszip.min-CkbBfwAQ.js → jszip.min-8hyJpgpx.js} +0 -0
  284. /package/dist/assets/{katex-TZJFa0J_.js → katex-ARhY9PS1.js} +0 -0
  285. /package/dist/assets/{middleware-B3k-TSmZ.js → middleware-DXsbf9xI.js} +0 -0
  286. /package/dist/assets/{path-DodfxcX7.js → path-10BjdyLh.js} +0 -0
  287. /package/dist/assets/{rough.esm-CUaa1Rbe.js → rough.esm-DxgC_14r.js} +0 -0
  288. /package/dist/assets/{xlsx-CxByi7bR.js → xlsx-DrDuDNAa.js} +0 -0
@@ -0,0 +1,301 @@
1
+ import { useState, type FormEvent, type ReactNode } from 'react';
2
+ import type { AppPackageView } from '@nextclaw/client-sdk';
3
+ import { Boxes, PackagePlus, RefreshCw, ShieldCheck, Store } from 'lucide-react';
4
+ import type { PanelAppEntryView } from '@/shared/lib/api';
5
+ import { useAppPresenter } from '@/app/components/app-presenter-provider';
6
+ import { AppMarketplaceDialog } from '@/features/apps/components/app-marketplace-dialog';
7
+ import { AppPackageCard } from '@/features/apps/components/app-package-card';
8
+ import {
9
+ useAppPackageMutation,
10
+ useAppPackages,
11
+ type AppPackageMutationInput,
12
+ } from '@/features/apps/hooks/use-app-packages';
13
+ import {
14
+ useGrantPanelAppClient,
15
+ usePanelApps,
16
+ useRecordPanelAppOpened,
17
+ } from '@/features/panel-apps';
18
+ import { Button } from '@/shared/components/ui/button';
19
+ import {
20
+ Dialog,
21
+ DialogContent,
22
+ DialogDescription,
23
+ DialogFooter,
24
+ DialogHeader,
25
+ DialogTitle,
26
+ } from '@/shared/components/ui/dialog';
27
+ import { Input } from '@/shared/components/ui/input';
28
+ import { Skeleton } from '@/shared/components/ui/skeleton';
29
+ import { t } from '@/shared/lib/i18n';
30
+
31
+ export function AppPackagesPanel({
32
+ headerContent,
33
+ onOpenPanelApp,
34
+ }: {
35
+ headerContent?: ReactNode;
36
+ onOpenPanelApp: (entry: PanelAppEntryView) => void;
37
+ }) {
38
+ const appPackages = useAppPackages();
39
+ const panelApps = usePanelApps();
40
+ const lifecycle = useAppPackageMutation();
41
+ const recordOpened = useRecordPanelAppOpened();
42
+ const grantClient = useGrantPanelAppClient();
43
+ const presenter = useAppPresenter();
44
+ const [installOpen, setInstallOpen] = useState(false);
45
+ const [installSource, setInstallSource] = useState('');
46
+ const [marketplaceOpen, setMarketplaceOpen] = useState(false);
47
+
48
+ const runMutation = (input: AppPackageMutationInput, onSuccess?: () => void) => {
49
+ lifecycle.reset();
50
+ lifecycle.mutate(input, { onSuccess });
51
+ };
52
+
53
+ const openPanelApp = async (entry: PanelAppEntryView) => {
54
+ if (entry.clientDeclared && !entry.clientGranted) {
55
+ const allowed = await presenter.serviceActionAuthorizationManager.requestAuthorization({
56
+ panelAppId: entry.appId,
57
+ actions: [{
58
+ actionId: 'nextclaw.client',
59
+ actionTitle: t('panelAppsClientGrantTitle'),
60
+ actionDescription: t('panelAppsClientGrantDescription'),
61
+ risk: 'dangerous',
62
+ }],
63
+ });
64
+ if (!allowed) return;
65
+ await grantClient.mutateAsync(entry.appId);
66
+ }
67
+ try {
68
+ onOpenPanelApp(await recordOpened.mutateAsync(entry.id));
69
+ } catch {
70
+ onOpenPanelApp(entry);
71
+ }
72
+ };
73
+
74
+ const submitInstall = (event: FormEvent) => {
75
+ event.preventDefault();
76
+ const source = installSource.trim();
77
+ if (!source) return;
78
+ runMutation({ action: 'install', source }, () => {
79
+ setInstallOpen(false);
80
+ setInstallSource('');
81
+ });
82
+ };
83
+
84
+ const refetch = () => {
85
+ void appPackages.refetch();
86
+ void panelApps.refetch();
87
+ };
88
+
89
+ return (
90
+ <div className="@container flex h-full min-h-0 flex-col bg-card text-card-foreground">
91
+ <div className="flex flex-col gap-2 border-b border-border/70 px-4 py-3 @[42rem]:flex-row @[42rem]:items-center @[42rem]:justify-between">
92
+ <div className="w-full min-w-0 overflow-x-auto @[42rem]:flex-1">{headerContent}</div>
93
+ <div className="ml-auto flex shrink-0 items-center gap-1">
94
+ <button
95
+ type="button"
96
+ onClick={refetch}
97
+ className="rounded-full p-2 text-muted-foreground transition-colors hover:bg-[var(--interaction-hover)] hover:text-accent-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-border"
98
+ title={t('appPackagesRefresh')}
99
+ aria-label={t('appPackagesRefresh')}
100
+ >
101
+ <RefreshCw className="h-3.5 w-3.5" />
102
+ </button>
103
+ <Button
104
+ type="button"
105
+ size="sm"
106
+ variant="outline"
107
+ onClick={() => {
108
+ lifecycle.reset();
109
+ setMarketplaceOpen(true);
110
+ }}
111
+ >
112
+ <Store className="mr-1.5 h-3.5 w-3.5" />
113
+ {t('appPackagesBrowse')}
114
+ </Button>
115
+ <Button
116
+ type="button"
117
+ size="sm"
118
+ variant="outline"
119
+ onClick={() => {
120
+ lifecycle.reset();
121
+ setInstallOpen(true);
122
+ }}
123
+ >
124
+ <PackagePlus className="mr-1.5 h-3.5 w-3.5" />
125
+ {t('appPackagesInstall')}
126
+ </Button>
127
+ </div>
128
+ </div>
129
+
130
+ <div className="custom-scrollbar min-h-0 flex-1 overflow-y-auto">
131
+ <div className="mx-auto w-full max-w-2xl p-3 sm:p-4">
132
+ <div className="mb-3 rounded-xl bg-gradient-to-br from-primary/[0.09] via-primary/[0.045] to-transparent px-3.5 py-3 ring-1 ring-primary/10">
133
+ <div className="flex items-start gap-2.5">
134
+ <span className="mt-0.5 flex h-8 w-8 shrink-0 items-center justify-center rounded-lg bg-primary/10 text-primary">
135
+ <Boxes className="h-4 w-4" />
136
+ </span>
137
+ <div>
138
+ <h1 className="text-sm font-semibold text-foreground">{t('appPackagesLibraryTitle')}</h1>
139
+ <p className="mt-0.5 text-xs leading-5 text-muted-foreground">
140
+ {t('appPackagesLibraryDescription')}
141
+ </p>
142
+ </div>
143
+ </div>
144
+ </div>
145
+
146
+ {lifecycle.isError ? (
147
+ <div role="alert" className="mb-3 rounded-xl bg-destructive/8 px-3 py-2.5 text-xs leading-5 text-destructive ring-1 ring-destructive/15">
148
+ {lifecycle.error instanceof Error ? lifecycle.error.message : t('appPackagesActionFailed')}
149
+ </div>
150
+ ) : null}
151
+
152
+ <AppPackageLibrary
153
+ error={getPackageLoadError(appPackages.error, panelApps.error)}
154
+ isLoading={appPackages.isLoading || panelApps.isLoading}
155
+ isPending={lifecycle.isPending}
156
+ mutationAppId={'appId' in (lifecycle.variables ?? {})
157
+ ? (lifecycle.variables as { appId: string }).appId
158
+ : undefined}
159
+ packages={appPackages.data?.entries ?? []}
160
+ panelApps={panelApps.data?.entries ?? []}
161
+ onInstall={() => setInstallOpen(true)}
162
+ onMutate={runMutation}
163
+ onOpenPanelApp={(entry) => void openPanelApp(entry)}
164
+ />
165
+ </div>
166
+ </div>
167
+
168
+ <AppMarketplaceDialog
169
+ error={lifecycle.error instanceof Error ? lifecycle.error : null}
170
+ installedPackages={appPackages.data?.entries ?? []}
171
+ installingSource={lifecycle.variables?.action === 'install'
172
+ ? lifecycle.variables.source
173
+ : undefined}
174
+ isPending={lifecycle.isPending}
175
+ open={marketplaceOpen}
176
+ onOpenChange={setMarketplaceOpen}
177
+ onInstall={(source, registryUrl) => runMutation({
178
+ action: 'install',
179
+ source,
180
+ registryUrl,
181
+ }, () => setMarketplaceOpen(false))}
182
+ />
183
+
184
+ <Dialog open={installOpen} onOpenChange={setInstallOpen}>
185
+ <DialogContent className="max-w-md [&>:last-child]:hidden">
186
+ <form onSubmit={submitInstall}>
187
+ <DialogHeader>
188
+ <DialogTitle>{t('appPackagesInstallTitle')}</DialogTitle>
189
+ <DialogDescription>{t('appPackagesInstallDescription')}</DialogDescription>
190
+ </DialogHeader>
191
+ <div className="mt-4 space-y-3">
192
+ <Input
193
+ autoFocus
194
+ value={installSource}
195
+ onChange={(event) => setInstallSource(event.target.value)}
196
+ placeholder={t('appPackagesInstallPlaceholder')}
197
+ aria-label={t('appPackagesInstallSource')}
198
+ />
199
+ <div className="flex items-start gap-2 rounded-xl bg-muted/60 px-3 py-2.5 text-xs leading-5 text-muted-foreground">
200
+ <ShieldCheck className="mt-0.5 h-3.5 w-3.5 shrink-0" />
201
+ <span>{t('appPackagesInstallTrustHint')}</span>
202
+ </div>
203
+ {lifecycle.isError ? (
204
+ <p role="alert" className="text-xs text-destructive">
205
+ {lifecycle.error instanceof Error ? lifecycle.error.message : t('appPackagesActionFailed')}
206
+ </p>
207
+ ) : null}
208
+ </div>
209
+ <DialogFooter className="mt-5 gap-2 sm:gap-0">
210
+ <Button type="button" variant="outline" onClick={() => setInstallOpen(false)}>
211
+ {t('cancel')}
212
+ </Button>
213
+ <Button type="submit" disabled={!installSource.trim() || lifecycle.isPending}>
214
+ {lifecycle.isPending ? t('appPackagesInstalling') : t('appPackagesInstall')}
215
+ </Button>
216
+ </DialogFooter>
217
+ </form>
218
+ </DialogContent>
219
+ </Dialog>
220
+ </div>
221
+ );
222
+ }
223
+
224
+ function AppPackageLibrary({
225
+ error,
226
+ isLoading,
227
+ isPending,
228
+ mutationAppId,
229
+ onInstall,
230
+ onMutate,
231
+ onOpenPanelApp,
232
+ packages,
233
+ panelApps,
234
+ }: {
235
+ error?: string;
236
+ isLoading: boolean;
237
+ isPending: boolean;
238
+ mutationAppId?: string;
239
+ onInstall: () => void;
240
+ onMutate: (input: AppPackageMutationInput) => void;
241
+ onOpenPanelApp: (entry: PanelAppEntryView) => void;
242
+ packages: AppPackageView[];
243
+ panelApps: PanelAppEntryView[];
244
+ }) {
245
+ if (isLoading) {
246
+ return (
247
+ <div className="space-y-2.5" aria-label={t('appPackagesLoading')}>
248
+ <Skeleton className="h-44 w-full rounded-2xl" />
249
+ <Skeleton className="h-32 w-full rounded-2xl" />
250
+ </div>
251
+ );
252
+ }
253
+ if (error) {
254
+ return (
255
+ <div className="rounded-xl border border-destructive/20 bg-destructive/5 px-4 py-4 text-sm text-destructive">
256
+ {error}
257
+ </div>
258
+ );
259
+ }
260
+ if (packages.length === 0) {
261
+ return (
262
+ <div className="flex flex-col items-center rounded-2xl border border-dashed border-border px-6 py-10 text-center">
263
+ <PackagePlus className="h-7 w-7 text-muted-foreground" />
264
+ <h2 className="mt-3 text-sm font-semibold text-foreground">{t('appPackagesEmptyTitle')}</h2>
265
+ <p className="mt-1 max-w-sm text-xs leading-5 text-muted-foreground">
266
+ {t('appPackagesEmptyDescription')}
267
+ </p>
268
+ <Button type="button" size="sm" className="mt-4" onClick={onInstall}>
269
+ {t('appPackagesInstall')}
270
+ </Button>
271
+ </div>
272
+ );
273
+ }
274
+ return (
275
+ <div className="space-y-2.5">
276
+ {packages.map((appPackage) => (
277
+ <AppPackageCard
278
+ key={appPackage.id}
279
+ appPackage={appPackage}
280
+ panelApps={panelApps.filter((entry) => entry.packageId === appPackage.id)}
281
+ isPending={isPending && mutationAppId === appPackage.id}
282
+ onEnable={() => onMutate({ action: 'enable', appId: appPackage.id })}
283
+ onDisable={() => onMutate({ action: 'disable', appId: appPackage.id })}
284
+ onUpdate={() => onMutate({ action: 'update', appId: appPackage.id })}
285
+ onRollback={(version) => onMutate({
286
+ action: 'rollback',
287
+ appId: appPackage.id,
288
+ version,
289
+ })}
290
+ onUninstall={() => onMutate({ action: 'uninstall', appId: appPackage.id })}
291
+ onOpenPanelApp={onOpenPanelApp}
292
+ />
293
+ ))}
294
+ </div>
295
+ );
296
+ }
297
+
298
+ function getPackageLoadError(...errors: unknown[]): string | undefined {
299
+ const error = errors.find((candidate): candidate is Error => candidate instanceof Error);
300
+ return error?.message || (errors.some(Boolean) ? t('appPackagesLoadFailed') : undefined);
301
+ }
@@ -1,11 +1,12 @@
1
- import { AppWindow, Server } from 'lucide-react';
1
+ import { AppWindow, Boxes, Server } from 'lucide-react';
2
2
  import type { PanelAppEntryView } from '@/shared/lib/api';
3
+ import { AppPackagesPanel } from '@/features/apps/components/app-packages-panel';
3
4
  import { PanelAppsList } from '@/features/panel-apps';
4
5
  import { ServiceAppsPanel } from '@/features/service-apps';
5
6
  import { Tabs, TabsList, TabsTrigger } from '@/shared/components/ui/tabs';
6
7
  import { t } from '@/shared/lib/i18n';
7
8
 
8
- export type AppsPanelTab = 'panel-apps' | 'service-apps';
9
+ export type AppsPanelTab = 'apps' | 'panel-apps' | 'service-apps';
9
10
 
10
11
  export function AppsPanel({
11
12
  activeTab,
@@ -18,7 +19,11 @@ export function AppsPanel({
18
19
  }) {
19
20
  const headerTabs = (
20
21
  <Tabs value={activeTab} onValueChange={(value) => onActiveTabChange(value as AppsPanelTab)}>
21
- <TabsList className="grid h-auto w-[min(340px,100%)] grid-cols-2 rounded-lg bg-gray-100/70 p-0.5">
22
+ <TabsList className="grid h-auto w-[min(390px,100%)] grid-cols-3 rounded-lg bg-muted/70 p-0.5">
23
+ <TabsTrigger value="apps" className="gap-1.5 px-2 py-1.5 text-xs">
24
+ <Boxes className="h-3.5 w-3.5" />
25
+ {t('appsTitle')}
26
+ </TabsTrigger>
22
27
  <TabsTrigger value="panel-apps" className="gap-1.5 px-2 py-1.5 text-xs">
23
28
  <AppWindow className="h-3.5 w-3.5" />
24
29
  {t('panelAppsTitle')}
@@ -33,7 +38,9 @@ export function AppsPanel({
33
38
 
34
39
  return (
35
40
  <div className="h-full min-h-0 bg-white">
36
- {activeTab === 'panel-apps' ? (
41
+ {activeTab === 'apps' ? (
42
+ <AppPackagesPanel headerContent={headerTabs} onOpenPanelApp={onOpenPanelApp} />
43
+ ) : activeTab === 'panel-apps' ? (
37
44
  <PanelAppsList headerContent={headerTabs} onOpenPanelApp={onOpenPanelApp} />
38
45
  ) : (
39
46
  <ServiceAppsPanel headerContent={headerTabs} />
@@ -0,0 +1,95 @@
1
+ import { useQuery } from '@tanstack/react-query';
2
+
3
+ const APP_MARKETPLACE_QUERY_KEY = ['app-marketplace'] as const;
4
+ const APP_MARKETPLACE_BASE_URL = 'https://apps-registry.nextclaw.io';
5
+
6
+ export type AppMarketplaceItemView = {
7
+ id: string;
8
+ slug: string;
9
+ appId: string;
10
+ name: string;
11
+ summary: string;
12
+ summaryI18n: Record<string, string>;
13
+ tags: string[];
14
+ latestVersion: string;
15
+ featured: boolean;
16
+ publisher: {
17
+ id: string;
18
+ name: string;
19
+ url?: string;
20
+ };
21
+ install: {
22
+ kind: 'registry';
23
+ spec: string;
24
+ registry: string;
25
+ };
26
+ webUrl: string;
27
+ };
28
+
29
+ export type AppMarketplaceListView = {
30
+ total: number;
31
+ items: AppMarketplaceItemView[];
32
+ };
33
+
34
+ export function useAppMarketplace(enabled: boolean) {
35
+ return useQuery({
36
+ queryKey: APP_MARKETPLACE_QUERY_KEY,
37
+ queryFn: fetchAppMarketplace,
38
+ enabled,
39
+ retry: 1,
40
+ staleTime: 5 * 60 * 1000,
41
+ });
42
+ }
43
+
44
+ async function fetchAppMarketplace(): Promise<AppMarketplaceListView> {
45
+ const response = await fetch(
46
+ `${APP_MARKETPLACE_BASE_URL}/api/v1/apps/items?page=1&pageSize=100`,
47
+ { headers: { accept: 'application/json' } },
48
+ );
49
+ if (!response.ok) {
50
+ throw new Error(`Marketplace request failed (${response.status})`);
51
+ }
52
+
53
+ const payload: unknown = await response.json();
54
+ if (!isRecord(payload) || payload.ok !== true || !isRecord(payload.data)) {
55
+ throw new Error('Marketplace returned an invalid response');
56
+ }
57
+
58
+ const items = Array.isArray(payload.data.items)
59
+ ? payload.data.items.filter(isAppMarketplaceItem)
60
+ : [];
61
+ return {
62
+ total: typeof payload.data.total === 'number' ? payload.data.total : items.length,
63
+ items,
64
+ };
65
+ }
66
+
67
+ function isAppMarketplaceItem(value: unknown): value is AppMarketplaceItemView {
68
+ if (!isRecord(value) || !isRecord(value.install) || !isRecord(value.publisher)) {
69
+ return false;
70
+ }
71
+ return typeof value.id === 'string'
72
+ && typeof value.slug === 'string'
73
+ && typeof value.appId === 'string'
74
+ && typeof value.name === 'string'
75
+ && typeof value.summary === 'string'
76
+ && isStringRecord(value.summaryI18n)
77
+ && Array.isArray(value.tags)
78
+ && value.tags.every((tag) => typeof tag === 'string')
79
+ && typeof value.latestVersion === 'string'
80
+ && typeof value.featured === 'boolean'
81
+ && typeof value.publisher.id === 'string'
82
+ && typeof value.publisher.name === 'string'
83
+ && value.install.kind === 'registry'
84
+ && typeof value.install.spec === 'string'
85
+ && typeof value.install.registry === 'string'
86
+ && typeof value.webUrl === 'string';
87
+ }
88
+
89
+ function isStringRecord(value: unknown): value is Record<string, string> {
90
+ return isRecord(value) && Object.values(value).every((entry) => typeof entry === 'string');
91
+ }
92
+
93
+ function isRecord(value: unknown): value is Record<string, unknown> {
94
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
95
+ }
@@ -0,0 +1,59 @@
1
+ import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
2
+ import { nextclawClient } from '@/shared/lib/api';
3
+
4
+ const APP_PACKAGES_QUERY_KEY = ['app-packages'] as const;
5
+ const PANEL_APPS_QUERY_KEY = ['panel-apps'] as const;
6
+ const SERVICE_APPS_QUERY_KEY = ['service-apps'] as const;
7
+ const SERVICE_ACTIONS_QUERY_KEY = ['service-actions'] as const;
8
+
9
+ export type AppPackageMutationInput =
10
+ | { action: 'enable'; appId: string }
11
+ | { action: 'disable'; appId: string }
12
+ | { action: 'install'; source: string; registryUrl?: string }
13
+ | { action: 'update'; appId: string }
14
+ | { action: 'rollback'; appId: string; version: string }
15
+ | { action: 'uninstall'; appId: string; purgeData?: boolean };
16
+
17
+ export function useAppPackages() {
18
+ return useQuery({
19
+ queryKey: APP_PACKAGES_QUERY_KEY,
20
+ queryFn: () => nextclawClient.appPackages.list(),
21
+ staleTime: 0,
22
+ });
23
+ }
24
+
25
+ export function useAppPackageMutation() {
26
+ const queryClient = useQueryClient();
27
+ return useMutation({
28
+ mutationFn: async (input: AppPackageMutationInput) => {
29
+ switch (input.action) {
30
+ case 'enable':
31
+ return await nextclawClient.appPackages.enable(input.appId);
32
+ case 'disable':
33
+ return await nextclawClient.appPackages.disable(input.appId);
34
+ case 'install':
35
+ return await nextclawClient.appPackages.install({
36
+ source: input.source,
37
+ registryUrl: input.registryUrl,
38
+ });
39
+ case 'update':
40
+ return await nextclawClient.appPackages.update(input.appId);
41
+ case 'rollback':
42
+ return await nextclawClient.appPackages.rollback(input.appId, input.version);
43
+ case 'uninstall':
44
+ return await nextclawClient.appPackages.uninstall(
45
+ input.appId,
46
+ input.purgeData ?? false,
47
+ );
48
+ }
49
+ },
50
+ onSuccess: async () => {
51
+ await Promise.all([
52
+ queryClient.invalidateQueries({ queryKey: APP_PACKAGES_QUERY_KEY }),
53
+ queryClient.invalidateQueries({ queryKey: PANEL_APPS_QUERY_KEY }),
54
+ queryClient.invalidateQueries({ queryKey: SERVICE_APPS_QUERY_KEY }),
55
+ queryClient.invalidateQueries({ queryKey: SERVICE_ACTIONS_QUERY_KEY }),
56
+ ]);
57
+ },
58
+ });
59
+ }
@@ -1,3 +1,4 @@
1
+ import { Clock3, Folder } from 'lucide-react';
1
2
  import { t } from '@/shared/lib/i18n';
2
3
  import { cn } from '@/shared/lib/utils';
3
4
 
@@ -5,22 +6,44 @@ type ChatSidebarListModeSwitchProps = { isProjectFirstView: boolean; onSelectMod
5
6
 
6
7
  export function ChatSidebarListModeSwitch({ isProjectFirstView, onSelectMode }: ChatSidebarListModeSwitchProps) {
7
8
  return (
8
- <div className="flex items-center gap-1.5 text-[11px]">
9
+ <div
10
+ role="group"
11
+ aria-label={t('chatSidebarViewMode')}
12
+ className="relative grid h-7 grid-cols-2 rounded-full bg-foreground/[0.04] p-0.5 text-[11px]"
13
+ >
14
+ <span
15
+ aria-hidden="true"
16
+ className={cn(
17
+ 'pointer-events-none absolute bottom-0.5 left-0.5 top-0.5 w-[calc(50%-2px)] rounded-full bg-gray-200/70 transition-transform duration-200 ease-out motion-reduce:transition-none',
18
+ isProjectFirstView && 'translate-x-full',
19
+ )}
20
+ />
9
21
  <button
10
22
  type="button"
11
23
  aria-pressed={!isProjectFirstView}
12
24
  onClick={() => onSelectMode('time-first')}
13
- className={cn('rounded-sm transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-border', isProjectFirstView ? 'text-muted-foreground/65 hover:text-muted-foreground' : 'font-medium text-foreground')}
25
+ className={cn(
26
+ 'relative z-10 inline-flex h-6 items-center justify-center gap-1 rounded-full px-2 transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1',
27
+ isProjectFirstView
28
+ ? 'text-muted-foreground hover:text-foreground'
29
+ : 'font-medium text-foreground',
30
+ )}
14
31
  >
32
+ <Clock3 aria-hidden="true" className="h-3.5 w-3.5" />
15
33
  {t('chatSidebarViewTime')}
16
34
  </button>
17
- <span className="text-muted-foreground/35">/</span>
18
35
  <button
19
36
  type="button"
20
37
  aria-pressed={isProjectFirstView}
21
38
  onClick={() => onSelectMode('project-first')}
22
- className={cn('rounded-sm transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-border', isProjectFirstView ? 'font-medium text-foreground' : 'text-muted-foreground/65 hover:text-muted-foreground')}
39
+ className={cn(
40
+ 'relative z-10 inline-flex h-6 items-center justify-center gap-1 rounded-full px-2 transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1',
41
+ isProjectFirstView
42
+ ? 'font-medium text-foreground'
43
+ : 'text-muted-foreground hover:text-foreground',
44
+ )}
23
45
  >
46
+ <Folder aria-hidden="true" className="h-3.5 w-3.5" />
24
47
  {t('chatSidebarViewProject')}
25
48
  </button>
26
49
  </div>
@@ -25,13 +25,34 @@ function renderSessionArea(isProjectFirstView: boolean) {
25
25
  }
26
26
 
27
27
  describe("ChatSidebarSessionArea", () => {
28
- it("reserves the action-row height while switching list modes", () => {
28
+ it("renders an accessible animated segmented control for list modes", () => {
29
29
  const { onSelectMode } = renderSessionArea(false);
30
+ const modeGroup = screen.getByRole("group", { name: "Session list view" });
31
+ const timeButton = screen.getByRole("button", { name: "Time" });
32
+ const projectButton = screen.getByRole("button", { name: "Project" });
30
33
 
31
- expect(
32
- screen.getByRole("button", { name: "Time" }).parentElement?.parentElement?.className,
33
- ).toContain("h-7");
34
- fireEvent.click(screen.getByRole("button", { name: "Project" }));
34
+ expect(modeGroup.className).toContain("h-7");
35
+ expect(modeGroup.className).toContain("rounded-full");
36
+ expect(modeGroup.className).toContain("bg-foreground/[0.04]");
37
+ expect(modeGroup.className).not.toContain("border");
38
+ expect(modeGroup.className).not.toContain("shadow-inner");
39
+ const modeIndicator = modeGroup.querySelector("span[aria-hidden='true']");
40
+ expect(modeIndicator?.className).toContain("rounded-full");
41
+ expect(modeIndicator?.className).toContain("bg-gray-200/70");
42
+ expect(modeIndicator?.className).not.toContain("shadow-");
43
+ expect(modeIndicator?.className).not.toContain("ring-");
44
+ expect(modeIndicator?.className).toContain("transition-transform");
45
+ expect(timeButton.getAttribute("aria-pressed")).toBe("true");
46
+ expect(projectButton.getAttribute("aria-pressed")).toBe("false");
47
+ expect(timeButton.className).toContain("rounded-full");
48
+ expect(projectButton.className).toContain("rounded-full");
49
+ expect(timeButton.querySelector("svg")).not.toBeNull();
50
+ expect(projectButton.querySelector("svg")).not.toBeNull();
51
+ expect(modeGroup.parentElement?.className).toContain("justify-end");
52
+ expect(modeGroup.parentElement?.className).toContain("h-8");
53
+ expect(modeGroup.parentElement?.lastElementChild).toBe(modeGroup);
54
+
55
+ fireEvent.click(projectButton);
35
56
 
36
57
  expect(onSelectMode).toHaveBeenCalledWith("project-first");
37
58
  });
@@ -39,12 +60,22 @@ describe("ChatSidebarSessionArea", () => {
39
60
  it("uses a folder-plus icon for the add-project action", () => {
40
61
  const { onAddProject } = renderSessionArea(true);
41
62
  const addProjectButton = screen.getByRole("button", { name: "Add Project" });
63
+ const modeIndicator = screen
64
+ .getByRole("group", { name: "Session list view" })
65
+ .querySelector("span[aria-hidden='true']");
42
66
 
43
67
  expect(
44
68
  addProjectButton
45
69
  .querySelector("svg")
46
70
  ?.classList.contains("lucide-folder-plus"),
47
71
  ).toBe(true);
72
+ expect(addProjectButton.className).toContain("hover:bg-gray-200/60");
73
+ expect(modeIndicator?.className).toContain("translate-x-full");
74
+ expect(modeIndicator?.className).toContain("motion-reduce:transition-none");
75
+ expect(screen.getByRole("button", { name: "Project" }).getAttribute("aria-pressed")).toBe(
76
+ "true",
77
+ );
78
+ expect(modeIndicator?.parentElement?.previousElementSibling).toBe(addProjectButton);
48
79
  fireEvent.click(addProjectButton);
49
80
 
50
81
  expect(onAddProject).toHaveBeenCalledOnce();
@@ -165,20 +165,19 @@ export function ChatSidebarSessionArea({
165
165
 
166
166
  return (
167
167
  <>
168
- <div className="flex items-center justify-end px-4 pb-1 pt-2">
169
- <div className="flex h-7 items-center gap-1.5">
170
- {isProjectFirstView ? (
171
- <IconActionButton
172
- icon={<FolderPlus className="h-3.5 w-3.5" />}
173
- label={t("chatProjectAdd")}
174
- onClick={onAddProject}
175
- />
176
- ) : null}
177
- <ChatSidebarListModeSwitch
178
- isProjectFirstView={isProjectFirstView}
179
- onSelectMode={onSelectMode}
168
+ <div className="flex h-8 items-center justify-end gap-1 px-3">
169
+ {isProjectFirstView ? (
170
+ <IconActionButton
171
+ icon={<FolderPlus className="h-3.5 w-3.5" />}
172
+ label={t("chatProjectAdd")}
173
+ tone="surface"
174
+ onClick={onAddProject}
180
175
  />
181
- </div>
176
+ ) : null}
177
+ <ChatSidebarListModeSwitch
178
+ isProjectFirstView={isProjectFirstView}
179
+ onSelectMode={onSelectMode}
180
+ />
182
181
  </div>
183
182
 
184
183
  <div