@nextclaw/ui 0.15.4 → 0.15.6

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 (300) hide show
  1. package/CHANGELOG.md +41 -0
  2. package/dist/assets/abnfDiagram-VRR7QNED-BYpK0F5F.js +1 -0
  3. package/dist/assets/{aiden0z-pptx-renderer.es-Bd99vut2.js → aiden0z-pptx-renderer.es-D87ywFdC.js} +1 -1
  4. package/dist/assets/api-Dyb5rgh0.js +13 -0
  5. package/dist/assets/app-presenter-provider-7jV0nPiL.js +106 -0
  6. package/dist/assets/{appearance-settings-page-fLIJbx1C.js → appearance-settings-page-DFm3BbEb.js} +1 -1
  7. package/dist/assets/arc-DFELHWM2.js +1 -0
  8. package/dist/assets/architecture-TIHT7OUA-ClwVFq-K.js +1 -0
  9. package/dist/assets/architectureDiagram-ZJ3FMSHR-CvRhdovL.js +36 -0
  10. package/dist/assets/array-BqYdCIE6.js +1 -0
  11. package/dist/assets/blockDiagram-677ZJIJ3-DVYNkbR_.js +132 -0
  12. package/dist/assets/{book-open-DWUjS6aW.js → book-open-L6LCuqOr.js} +1 -1
  13. package/dist/assets/c4Diagram-LMCZKHZV-B1w-iwRe.js +10 -0
  14. package/dist/assets/channel-BEHOSFc5.js +1 -0
  15. package/dist/assets/{channels-list-page-CvCTJraa.js → channels-list-page-DOaPKNJA.js} +2 -2
  16. package/dist/assets/{chat-page-B42UUIFR.js → chat-page-Cw9uDhD_.js} +1 -1
  17. package/dist/assets/chunk-2Q5K7J3B-DrIhesSX.js +1 -0
  18. package/dist/assets/chunk-32BRIVSS-kG3lg0bC.js +1 -0
  19. package/dist/assets/chunk-52WLFC77-BLeB7iGk.js +10 -0
  20. package/dist/assets/chunk-5FCAYU7R-BQga1Rr7.js +1 -0
  21. package/dist/assets/chunk-5HE753X5-DU81hkcF.js +1 -0
  22. package/dist/assets/chunk-5JV3BV7I-C2bAx0m7.js +1 -0
  23. package/dist/assets/chunk-5TONJI2A-BExIIgWY.js +2 -0
  24. package/dist/assets/chunk-5VM5RSS4-C3XB05yh.js +15 -0
  25. package/dist/assets/chunk-7BUUIJ7U-JiyRtrGu.js +1 -0
  26. package/dist/assets/chunk-BIQX33UG-BZRV20pI.js +1 -0
  27. package/dist/assets/chunk-C7G6YPKG-AsyDSQ9j.js +1 -0
  28. package/dist/assets/chunk-CQNSW5MT-iN-fiXVA.js +1 -0
  29. package/dist/assets/chunk-CYSBUYHQ-DhC_TRzI.js +1 -0
  30. package/dist/assets/chunk-EMLP6XTP-QFLK8R6d.js +1 -0
  31. package/dist/assets/chunk-EX3LRPZG-DzUwhkCz.js +231 -0
  32. package/dist/assets/chunk-FWX5IMBZ-DFelpZBy.js +2 -0
  33. package/dist/assets/chunk-HOUHSVGY-BMPwFEwN.js +1 -0
  34. package/dist/assets/chunk-ICXQ74PX-DH8NJMok.js +2 -0
  35. package/dist/assets/chunk-JG7HCLWE-D-95AsO4.js +2 -0
  36. package/dist/assets/chunk-JWPE2WC7-DIaTv8kx.js +1 -0
  37. package/dist/assets/chunk-KEIR6QF5-A3jEqwzn.js +161 -0
  38. package/dist/assets/chunk-MOJQB5TN-BPFLzWgj.js +88 -0
  39. package/dist/assets/chunk-MOZMSUNE-DR_fzxG0.js +1 -0
  40. package/dist/assets/chunk-OGEWGWER-TIcEoPPH.js +1 -0
  41. package/dist/assets/chunk-OSBZ3O6U-BALNvSfG.js +1 -0
  42. package/dist/assets/chunk-PUDLZKDR-I-EXhf_k.js +156 -0
  43. package/dist/assets/chunk-Q4XR5HBZ-uvIGstBS.js +70 -0
  44. package/dist/assets/chunk-QBLGF6JB-r-TjLdxa.js +1 -0
  45. package/dist/assets/chunk-R7FJI6CG-N-1LDgvy.js +1 -0
  46. package/dist/assets/chunk-RYQCIY6F-jxNASx65.js +1 -0
  47. package/dist/assets/chunk-U6XO7XAA-Cphbuc7q.js +2 -0
  48. package/dist/assets/chunk-V7JOEXUC-BLEuBf1s.js +206 -0
  49. package/dist/assets/chunk-VAUOI2AC-Dpcz6BhT.js +1 -0
  50. package/dist/assets/chunk-VR4S4FIN-BAUbR0ke.js +1 -0
  51. package/dist/assets/chunk-WYO6CB5R-Do6SJrnY.js +127 -0
  52. package/dist/assets/chunk-XXDRQBXY-C2QjjwYX.js +1 -0
  53. package/dist/assets/chunk-Y2CYZVJY-BGrd8tLy.js +1 -0
  54. package/dist/assets/chunk-YOTPTUD7-CQqvMTCr.js +1 -0
  55. package/dist/assets/chunk-ZGVPDNZ5-B7kWWaXv.js +62 -0
  56. package/dist/assets/chunk-ZIRB5QZD-DTU0cDNh.js +32 -0
  57. package/dist/assets/classDiagram-OUVF2IWQ-DESMaY3_.js +1 -0
  58. package/dist/assets/classDiagram-v2-EOCWNBFH-DwOlByYH.js +1 -0
  59. package/dist/assets/{config-split-page-DBgt9S8b.js → config-split-page-CqlqjnCK.js} +1 -1
  60. package/dist/assets/{confirm-dialog-GSXIvoRU.js → confirm-dialog-bAp8NU93.js} +1 -1
  61. package/dist/assets/cose-bilkent-JH36ORCC-B9LrtV-q.js +1 -0
  62. package/dist/assets/cynefin-VYW2F7L2-CzjeeOq9.js +1 -0
  63. package/dist/assets/cynefinDiagram-TSTJHNR4-BfjvW4su.js +62 -0
  64. package/dist/assets/cytoscape.esm-CRW8q92n.js +321 -0
  65. package/dist/assets/dagre-DkiXZKkL.js +1 -0
  66. package/dist/assets/dagre-VKFMJZFB-B1pQKQSY.js +4 -0
  67. package/dist/assets/defaultLocale-Cdv9x6q7.js +1 -0
  68. package/dist/assets/desktop-update-config-2SjMfCMl.js +1 -0
  69. package/dist/assets/diagram-FQU43EPY-cB4EZdaP.js +3 -0
  70. package/dist/assets/diagram-G47NLZAW-CtW8im1B.js +24 -0
  71. package/dist/assets/diagram-NH7WQ7WH-BDCtrCCm.js +24 -0
  72. package/dist/assets/diagram-OA4YK3LP-DEMJ42Gt.js +30 -0
  73. package/dist/assets/diagram-WEI45ONY-CdCcxSYU.js +41 -0
  74. package/dist/assets/{dist-DC7f4TJH.js → dist-CrtbGLlh.js} +1 -1
  75. package/dist/assets/dist-dw-gAfM_.js +1 -0
  76. package/dist/assets/doc-browser-A5IRyDXQ2.js +1 -0
  77. package/dist/assets/{doc-browser-D0monHjG.js → doc-browser-BmnQa30-.js} +1 -1
  78. package/dist/assets/{doc-browser-context-CGwEOhBy.js → doc-browser-context-BlpUUFrR.js} +1 -1
  79. package/dist/assets/{docx-preview-eFH9DRsS.js → docx-preview-zis3U0Fa.js} +1 -1
  80. package/dist/assets/{download-DiP9zx-J.js → download-BhZZNXD8.js} +1 -1
  81. package/dist/assets/ebnfDiagram-CCIWWBDH-DBZETBwE.js +1 -0
  82. package/dist/assets/erDiagram-Q63AITRT-y2zDXprP.js +85 -0
  83. package/dist/assets/eventmodeling-45OFAUF4-C38BqG8w.js +1 -0
  84. package/dist/assets/{external-link-C6ZeSPef.js → external-link-B9Uoq5J7.js} +1 -1
  85. package/dist/assets/{eye-suvKmtwn.js → eye-K5knauEM.js} +1 -1
  86. package/dist/assets/flowDiagram-23GEKE2U-BoKk8RRq.js +1 -0
  87. package/dist/assets/{folder-CMELbQGG.js → folder-BIYZZWJv.js} +1 -1
  88. package/dist/assets/{form-actions-CUAzpNe_.js → form-actions-CKprWFeM.js} +1 -1
  89. package/dist/assets/ganttDiagram-NO4QXBWP-D2DP344e.js +292 -0
  90. package/dist/assets/gitGraph-TEB2WS4Q-IsQHHJuS.js +1 -0
  91. package/dist/assets/gitGraphDiagram-IHSO6WYX-D37uiwA2.js +106 -0
  92. package/dist/assets/graphlib-BcNFQr5y.js +1 -0
  93. package/dist/assets/index-BVn702m4.css +1 -0
  94. package/dist/assets/index-CFxJ1Fkg.js +2 -0
  95. package/dist/assets/info-DKCQHKI2-ofy2qlHI.js +1 -0
  96. package/dist/assets/infoDiagram-FWYZ7A6U-CG-WAIRi.js +2 -0
  97. package/dist/assets/init-D8TjIqfm.js +1 -0
  98. package/dist/assets/ishikawaDiagram-FXEZZL3T-WeCTnW0f.js +70 -0
  99. package/dist/assets/journeyDiagram-5HDEW3XC-DUFBW9Td.js +139 -0
  100. package/dist/assets/kanban-definition-HUTT4EX6-B0lxgMpg.js +89 -0
  101. package/dist/assets/katex-wEmrEFTg.js +257 -0
  102. package/dist/assets/line-B78frP_c.js +1 -0
  103. package/dist/assets/linear-C6lVEcb2.js +1 -0
  104. package/dist/assets/loader-circle-Bj2RMrCR.js +1 -0
  105. package/dist/assets/{logo-badge-Cu5UGH_4.js → logo-badge-BQ-XFZod.js} +1 -1
  106. package/dist/assets/map-Bdri5UL5.js +1 -0
  107. package/dist/assets/mcp-marketplace-page-BfA7A3n4.js +9 -0
  108. package/dist/assets/mcp-marketplace-page-CXHYO95j.js +1 -0
  109. package/dist/assets/mermaid-parser.core-Cue18vLo.js +4 -0
  110. package/dist/assets/mermaid.core-CXwWRh5i.js +11 -0
  111. package/dist/assets/mindmap-definition-LN4V7U3C-CFxz0P0-.js +96 -0
  112. package/dist/assets/{model-config-page-CWP6pyBm.js → model-config-page-BbWbW_qM.js} +1 -1
  113. package/dist/assets/ordinal-Cbd8t4LB.js +1 -0
  114. package/dist/assets/packet-7NZHBO7P-Rfopfa1o.js +1 -0
  115. package/dist/assets/path-DzBnMI1r.js +1 -0
  116. package/dist/assets/pegDiagram-2B236MQR-B_Qt9zyT.js +1 -0
  117. package/dist/assets/pie-RZYD4A2V-CJR1xKEv.js +1 -0
  118. package/dist/assets/pieDiagram-ENE6RG2P-CRQdSuwS.js +39 -0
  119. package/dist/assets/plus-TmFdwO3P.js +1 -0
  120. package/dist/assets/{popover-DImguRok.js → popover-CX9zFsuc.js} +1 -1
  121. package/dist/assets/preload-helper-D4M6sveU.js +1 -0
  122. package/dist/assets/{provider-scoped-model-input-CE7p4SbJ.js → provider-scoped-model-input-B-9_DtIP.js} +1 -1
  123. package/dist/assets/providers-config-page-xwIjGur2.js +1 -0
  124. package/dist/assets/quadrantDiagram-ABIIQ3AL-D5kPpttq.js +7 -0
  125. package/dist/assets/radar-I7S5WNFK-CDg8Z-1r.js +1 -0
  126. package/dist/assets/railroad-3IZDKUUU-BHnNmjxO.js +1 -0
  127. package/dist/assets/railroad-abnf-AHOZXSZD-DGtNcusW.js +1 -0
  128. package/dist/assets/railroad-ebnf-EBAXGLYW-BRFtsQmm.js +1 -0
  129. package/dist/assets/railroad-peg-LSFZ7HO6-Bj5IypCA.js +1 -0
  130. package/dist/assets/railroadDiagram-RFXS5EU6-DGh_TGlQ.js +1 -0
  131. package/dist/assets/{react-5sjAT_6d.js → react-DsgudwpT.js} +1 -1
  132. package/dist/assets/{refresh-cw-CHLBzWWj.js → refresh-cw-kXdvbhFf.js} +1 -1
  133. package/dist/assets/remote-B9F2uCk9.js +1 -0
  134. package/dist/assets/requirementDiagram-TGXJPOKE-EmFph0H_.js +84 -0
  135. package/dist/assets/rotate-cw-DEc6aihr.js +1 -0
  136. package/dist/assets/rough.esm-wJ1d3nQl.js +1 -0
  137. package/dist/assets/runtime-config-page-DTsi7czJ.js +1 -0
  138. package/dist/assets/sankeyDiagram-HTMAVEWB-BXWB5Ugi.js +40 -0
  139. package/dist/assets/{save-BABzyrjB.js → save-C9qpz2_2.js} +1 -1
  140. package/dist/assets/{search-BTqxG3ZG.js → search-_kEi29qo.js} +1 -1
  141. package/dist/assets/{search-config-page-BkKKmxmS.js → search-config-page-H7ug8f4D.js} +1 -1
  142. package/dist/assets/{secrets-config-page-CWJMyu-k.js → secrets-config-page-C1_6rINu.js} +2 -2
  143. package/dist/assets/security-config-Cr2ERKLu.js +1 -0
  144. package/dist/assets/{select-TYZDBNmq.js → select-MJKhKAWR.js} +1 -1
  145. package/dist/assets/sequenceDiagram-DBY2YBRQ-ClKnO7v0.js +162 -0
  146. package/dist/assets/{setting-row-DR23Z_5I.js → setting-row-DA3tJuew.js} +1 -1
  147. package/dist/assets/{settings-2-D8WWcBnw.js → settings-2-D_y7zXJz.js} +1 -1
  148. package/dist/assets/sizeCapture-X5ZJPWSS-DSo-t5ou.js +1 -0
  149. package/dist/assets/{skeleton-CloHeg0S.js → skeleton-0uTLUhm9.js} +1 -1
  150. package/dist/assets/src-Dad2ZbCt.js +1 -0
  151. package/dist/assets/stateDiagram-2N3HPSRC-BvAalBfz.js +1 -0
  152. package/dist/assets/stateDiagram-v2-6OUMAXLB-CfMU38yc.js +1 -0
  153. package/dist/assets/swimlanes-5IMT3BWC-DdVqVa1a.js +2 -0
  154. package/dist/assets/swimlanesDiagram-G3AALYLV-D9Zb9X57.js +8 -0
  155. package/dist/assets/{tabs-CX-ckWoo.js → tabs-h44XPocd.js} +1 -1
  156. package/dist/assets/{tag-chip-mCc23Rhb.js → tag-chip-bRZ04q3C.js} +1 -1
  157. package/dist/assets/timeline-definition-FHXFAJF6-D4ar8IQY.js +120 -0
  158. package/dist/assets/{trash-2-SAW1Z4Po.js → trash-2-BRpSE-Wt.js} +1 -1
  159. package/dist/assets/treeView-QDETBFTQ-Uy7M0WvH.js +1 -0
  160. package/dist/assets/treemap-6X3UGDF4-BJfGV7KX.js +1 -0
  161. package/dist/assets/use-config-CwDEAl6f.js +1 -0
  162. package/dist/assets/{use-confirm-dialog-C95F6sT1.js → use-confirm-dialog-BbnWw-8c.js} +1 -1
  163. package/dist/assets/{use-viewport-layout-C6m3Nte7.js → use-viewport-layout-CqgIMDQL.js} +1 -1
  164. package/dist/assets/vennDiagram-L72KCM5P-CDXJ_i3p.js +34 -0
  165. package/dist/assets/wardley-OPB4EBWU-q263iF3K.js +1 -0
  166. package/dist/assets/wardleyDiagram-EHGQE667-D-dSuT_7.js +78 -0
  167. package/dist/assets/x-DdEDL6ju.js +1 -0
  168. package/dist/assets/xychartDiagram-FW5EYKEG-C9JCzsZF.js +7 -0
  169. package/dist/index.html +33 -32
  170. package/package.json +8 -8
  171. package/src/features/chat/components/chat-popover-content.tsx +1 -26
  172. package/src/features/chat/components/conversation/__tests__/chat-conversation-header-section.test.tsx +84 -1
  173. package/src/features/chat/components/conversation/chat-conversation-header-section.tsx +13 -2
  174. package/src/features/chat/components/conversation/chat-conversation-workspace-section.tsx +1 -0
  175. package/src/features/chat/components/layout/__tests__/chat-sidebar-test.utils.ts +22 -0
  176. package/src/features/chat/components/layout/__tests__/chat-sidebar.test.tsx +13 -24
  177. package/src/features/chat/components/layout/chat-sidebar-desktop-layout.tsx +16 -4
  178. package/src/features/chat/components/layout/chat-sidebar.tsx +38 -2
  179. package/src/features/chat/features/conversation/components/__tests__/session-conversation-area.test.tsx +1 -0
  180. package/src/features/chat/features/conversation/components/__tests__/session-conversation-input.streaming.test.tsx +52 -2
  181. package/src/features/chat/features/conversation/components/session-conversation-area.tsx +22 -0
  182. package/src/features/chat/features/conversation/components/session-conversation-input.tsx +36 -20
  183. package/src/features/chat/features/conversation/hooks/__tests__/use-session-conversation-input-state.test.tsx +55 -0
  184. package/src/features/chat/features/conversation/hooks/use-session-conversation-input-state.ts +12 -11
  185. package/src/features/chat/features/conversation/hooks/use-session-conversation-preference-actions.ts +94 -0
  186. package/src/features/chat/features/input/hooks/__tests__/use-chat-input-surface-state.test.tsx +37 -0
  187. package/src/features/chat/features/input/hooks/use-chat-input-surface-state.ts +140 -63
  188. package/src/features/chat/features/input/input-surface-plugins/__tests__/context-reference-plugin.test.ts +175 -0
  189. package/src/features/chat/features/input/input-surface-plugins/__tests__/slash-command-plugin.test.ts +66 -0
  190. package/src/features/chat/features/input/input-surface-plugins/chat-input-product-plugin-adapters.types.ts +8 -1
  191. package/src/features/chat/features/input/input-surface-plugins/context-reference-plugin.utils.ts +274 -0
  192. package/src/features/chat/features/input/input-surface-plugins/slash-command-plugin.utils.ts +18 -11
  193. package/src/features/chat/features/input/utils/__tests__/chat-composer-state.utils.test.ts +26 -0
  194. package/src/features/chat/features/input/utils/__tests__/chat-inline-token.utils.test.ts +50 -4
  195. package/src/features/chat/features/input/utils/__tests__/chat-input-bar.utils.test.ts +51 -0
  196. package/src/features/chat/features/input/utils/chat-composer-state.utils.ts +4 -10
  197. package/src/features/chat/features/input/utils/chat-composer-token-protocol.utils.ts +29 -0
  198. package/src/features/chat/features/input/utils/chat-inline-token.utils.ts +66 -8
  199. package/src/features/chat/features/input/utils/chat-input-bar.utils.ts +62 -21
  200. package/src/features/chat/features/message/components/__tests__/chat-message-list.container.test.tsx +42 -10
  201. package/src/features/chat/features/message/components/chat-inline-file-preview.tsx +80 -0
  202. package/src/features/chat/features/message/components/chat-message-list.container.tsx +55 -60
  203. package/src/features/chat/features/message/utils/chat-message-texts.utils.ts +2 -0
  204. package/src/features/chat/features/ncp/hooks/__tests__/use-hydrated-ncp-agent.test.tsx +41 -3
  205. package/src/features/chat/features/project/components/__tests__/chat-project-create-dialog.test.tsx +67 -0
  206. package/src/features/chat/features/project/components/chat-project-create-dialog.tsx +203 -0
  207. package/src/features/chat/features/session/components/session-header/__tests__/chat-session-header-actions.test.tsx +7 -7
  208. package/src/features/chat/features/session/components/session-header/__tests__/chat-session-project-dialog.test.tsx +47 -0
  209. package/src/features/chat/features/session/components/session-header/chat-session-header-actions.tsx +6 -3
  210. package/src/features/chat/features/session/components/session-header/chat-session-project-badge.tsx +3 -0
  211. package/src/features/chat/features/session/components/session-header/chat-session-project-dialog.tsx +3 -0
  212. package/src/features/chat/features/session/components/session-header/chat-session-switcher-groups.tsx +245 -0
  213. package/src/features/chat/features/session/components/session-header/chat-session-title-switcher.tsx +81 -127
  214. package/src/features/chat/features/session/utils/__tests__/chat-sidebar-session-groups.utils.test.ts +22 -0
  215. package/src/features/chat/features/session/utils/chat-run-metadata.utils.ts +6 -6
  216. package/src/features/chat/features/session/utils/chat-sidebar-session-groups.utils.ts +12 -1
  217. package/src/features/chat/features/welcome/components/chat-conversation-welcome.tsx +3 -0
  218. package/src/features/chat/features/welcome/components/chat-welcome.tsx +1 -0
  219. package/src/features/chat/features/welcome/utils/__tests__/chat-welcome-project-options.utils.test.ts +20 -0
  220. package/src/features/chat/features/welcome/utils/chat-welcome-project-options.utils.ts +15 -2
  221. package/src/features/chat/features/workspace/components/__tests__/chat-session-workspace-file-preview.test.tsx +22 -24
  222. package/src/features/chat/features/workspace/components/__tests__/chat-session-workspace-panel.test.tsx +2 -1
  223. package/src/features/chat/features/workspace/components/chat-session-workspace-file-breadcrumbs.tsx +6 -17
  224. package/src/features/chat/features/workspace/components/chat-session-workspace-file-preview.tsx +41 -19
  225. package/src/features/chat/features/workspace/components/chat-session-workspace-panel-nav.tsx +5 -1
  226. package/src/features/chat/features/workspace/components/chat-session-workspace-panel.tsx +5 -1
  227. package/src/features/chat/features/workspace/utils/__tests__/chat-workspace-panel-view-model.utils.test.ts +104 -3
  228. package/src/features/chat/features/workspace/utils/chat-thread-workspace-session.utils.ts +83 -0
  229. package/src/features/chat/features/workspace/utils/chat-workspace-file-tab.utils.ts +4 -1
  230. package/src/features/chat/features/workspace/utils/chat-workspace-panel-view-model.utils.ts +96 -25
  231. package/src/features/chat/managers/__tests__/chat-thread-workspace.manager.test.ts +25 -0
  232. package/src/features/chat/managers/__tests__/chat-thread.manager.test.ts +5 -1
  233. package/src/features/chat/managers/chat-session-list.manager.ts +1 -0
  234. package/src/features/chat/managers/chat-thread.manager.ts +15 -37
  235. package/src/features/chat/pages/__tests__/ncp-chat-page.test.ts +23 -0
  236. package/src/features/chat/stores/__tests__/chat-thread.store.test.ts +9 -0
  237. package/src/features/chat/stores/chat-thread.store.ts +16 -36
  238. package/src/features/chat/types/chat-input-bar.types.ts +2 -0
  239. package/src/features/settings/components/config/provider-form.tsx +0 -4
  240. package/src/features/settings/utils/__tests__/provider-form-support.utils.test.ts +7 -9
  241. package/src/features/settings/utils/provider-form-model.utils.ts +0 -4
  242. package/src/features/system-status/hooks/use-current-version-release-notes-link.ts +33 -0
  243. package/src/features/system-status/index.ts +9 -0
  244. package/src/features/system-status/utils/update-release-notes.utils.ts +44 -3
  245. package/src/index.css +10 -16
  246. package/src/shared/components/common/__tests__/brand-header.test.tsx +127 -10
  247. package/src/shared/components/common/brand-header.tsx +142 -56
  248. package/src/shared/components/path-picker/__tests__/server-path-picker-dialog.test.tsx +288 -38
  249. package/src/shared/components/path-picker/server-path-picker-dialog.tsx +290 -199
  250. package/src/shared/components/path-picker/server-path-picker-directory-list.tsx +103 -0
  251. package/src/shared/components/path-picker/server-path-picker-footer.tsx +58 -0
  252. package/src/shared/components/path-picker/server-path-picker-locations.tsx +148 -0
  253. package/src/shared/components/path-picker/server-path-picker-navigation-bar.tsx +198 -0
  254. package/src/shared/components/path-picker/server-path-picker-new-folder.tsx +59 -0
  255. package/src/shared/components/path-picker/server-path-picker-toolbar.tsx +25 -0
  256. package/src/shared/hooks/use-projects.ts +28 -0
  257. package/src/shared/hooks/use-server-path-create-directory.ts +6 -0
  258. package/src/shared/hooks/use-server-path-read.ts +10 -5
  259. package/src/shared/hooks/use-server-path-search.ts +40 -0
  260. package/src/shared/lib/api/index.ts +1 -0
  261. package/src/shared/lib/api/projects/project.utils.ts +14 -0
  262. package/src/shared/lib/api/types.ts +30 -3
  263. package/src/shared/lib/api/utils/server-path.utils.ts +22 -1
  264. package/src/shared/lib/i18n/locales/en-US/chat.json +43 -2
  265. package/src/shared/lib/i18n/locales/en-US/core.json +2 -3
  266. package/src/shared/lib/i18n/locales/en-US/desktop-update.json +4 -0
  267. package/src/shared/lib/i18n/locales/en-US/path-picker.json +25 -0
  268. package/src/shared/lib/i18n/locales/zh-CN/chat.json +43 -2
  269. package/src/shared/lib/i18n/locales/zh-CN/core.json +2 -3
  270. package/src/shared/lib/i18n/locales/zh-CN/desktop-update.json +4 -0
  271. package/src/shared/lib/i18n/locales/zh-CN/path-picker.json +25 -0
  272. package/src/shared/lib/session-project/__tests__/workspace-file-breadcrumb.test.ts +1 -4
  273. package/src/shared/lib/session-project/workspace-file-breadcrumb.utils.ts +1 -18
  274. package/dist/assets/api-BOXOrpcJ.js +0 -13
  275. package/dist/assets/app-presenter-provider-CkHLbvZQ.js +0 -106
  276. package/dist/assets/desktop-update-config-7bmNeWIC.js +0 -1
  277. package/dist/assets/doc-browser-CgdoLUT92.js +0 -1
  278. package/dist/assets/index-BFeNDQ4R.css +0 -1
  279. package/dist/assets/index-D_6U-M94.js +0 -2
  280. package/dist/assets/loader-circle-tYHStefV.js +0 -1
  281. package/dist/assets/mcp-marketplace-page-BUzHDwW6.js +0 -9
  282. package/dist/assets/mcp-marketplace-page-ohUAdfqJ.js +0 -1
  283. package/dist/assets/plus-tywizkeY.js +0 -1
  284. package/dist/assets/providers-config-page-BKPo8SjH.js +0 -1
  285. package/dist/assets/remote-DXJryUxB.js +0 -1
  286. package/dist/assets/rotate-cw-BefO5brU.js +0 -1
  287. package/dist/assets/runtime-config-page-1Axv4QGu.js +0 -1
  288. package/dist/assets/security-config-DeQUIXfi.js +0 -1
  289. package/dist/assets/use-config-BucdaK23.js +0 -1
  290. package/dist/assets/x-DtCPHwCu.js +0 -1
  291. package/src/features/chat/components/__tests__/chat-popover-content.test.tsx +0 -41
  292. package/src/features/chat/components/layout/__tests__/chat-sidebar-utility-menu.test.tsx +0 -55
  293. package/src/features/chat/features/input/input-surface-plugins/__tests__/panel-app-reference-plugin.test.ts +0 -77
  294. package/src/features/chat/features/input/input-surface-plugins/panel-app-reference-plugin.utils.ts +0 -144
  295. /package/dist/assets/{config-hints-fDrYfl0l.js → config-hints-LxG80Uj5.js} +0 -0
  296. /package/dist/assets/{host-capabilities-zZvY9U63.js → host-capabilities-CFbAfrrA.js} +0 -0
  297. /package/dist/assets/{jszip.min-Das2IAnb.js → jszip.min-CP5uPNQa.js} +0 -0
  298. /package/dist/assets/{middleware-BLMHv9YF.js → middleware-CAXxl-af.js} +0 -0
  299. /package/dist/assets/{provider-models-C_yOh6DE.js → provider-models-BoUhFo9R.js} +0 -0
  300. /package/dist/assets/{xlsx-B6zVt1RK.js → xlsx-DUqJhg5u.js} +0 -0
@@ -1,10 +1,20 @@
1
1
  import type { UpdateSnapshot } from '@nextclaw/shared';
2
- import { runtimeUpdateManager, useRuntimeUpdateStore } from '@/features/system-status';
2
+ import {
3
+ runtimeUpdateManager,
4
+ resolveUpdateReleaseNotesLink,
5
+ useCurrentVersionReleaseNotesLink,
6
+ useRuntimeUpdateStore,
7
+ type ReleaseNotesLink,
8
+ type RuntimeUpdateBusyAction
9
+ } from '@/features/system-status';
3
10
  import { useAppMeta } from '@/shared/hooks/use-app-meta';
4
- import { type ReactNode, useState } from 'react';
11
+ import type { ReactNode } from 'react';
5
12
  import { RuntimeStatusEntry } from '@/app/components/layout/runtime-status-entry';
13
+ import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/shared/components/ui/tooltip';
14
+ import { hostCapabilityManager } from '@/shared/lib/host-capabilities';
6
15
  import { t } from '@/shared/lib/i18n';
7
16
  import { cn } from '@/shared/lib/utils';
17
+ import { ExternalLink } from 'lucide-react';
8
18
 
9
19
  type BrandHeaderProps = {
10
20
  className?: string;
@@ -14,9 +24,11 @@ type BrandHeaderProps = {
14
24
 
15
25
  export function BrandHeader({ className, density = 'sidebar', suffix }: BrandHeaderProps) {
16
26
  const { data } = useAppMeta();
27
+ const { supported, busyAction, snapshot } = useRuntimeUpdateStore();
17
28
  const productName = data?.name ?? 'NextClaw';
18
29
  const productVersion = data?.productVersion?.trim();
19
30
  const versionLabel = productVersion ? `v${productVersion}` : null;
31
+ const releaseNotesLink = useCurrentVersionReleaseNotesLink(productVersion);
20
32
  const resolvedSuffix = suffix ?? <RuntimeStatusEntry />;
21
33
  const shouldReserveMacWindowControls = typeof window !== 'undefined' && window.nextclawDesktop?.platform === 'darwin';
22
34
  const isChromeDensity = density === 'chrome';
@@ -41,9 +53,9 @@ export function BrandHeader({ className, density = 'sidebar', suffix }: BrandHea
41
53
  >
42
54
  {productName}
43
55
  </span>
44
- {versionLabel ? <BrandVersionLabel versionLabel={versionLabel} density={density} /> : null}
56
+ {versionLabel ? <BrandVersionLabel versionLabel={versionLabel} density={density} releaseNotesLink={releaseNotesLink} /> : null}
45
57
  </div>
46
- <RuntimeUpdateInlineStatus />
58
+ <RuntimeUpdateInlineStatus supported={supported} busyAction={busyAction} snapshot={snapshot} />
47
59
  {resolvedSuffix ? <span className="inline-flex items-center shrink-0">{resolvedSuffix}</span> : null}
48
60
  </div>
49
61
  </div>
@@ -53,75 +65,143 @@ export function BrandHeader({ className, density = 'sidebar', suffix }: BrandHea
53
65
  function BrandVersionLabel({
54
66
  versionLabel,
55
67
  density,
68
+ releaseNotesLink,
56
69
  }: {
57
70
  versionLabel: string;
58
71
  density: BrandHeaderProps['density'];
72
+ releaseNotesLink: ReleaseNotesLink | null;
59
73
  }) {
60
- const [isTooltipOpen, setIsTooltipOpen] = useState(false);
61
74
  const isChromeDensity = density === 'chrome';
62
-
63
- return (
64
- <span
65
- className="relative min-w-0 flex-1"
66
- onMouseEnter={() => setIsTooltipOpen(true)}
67
- onMouseLeave={() => setIsTooltipOpen(false)}
68
- onFocus={() => setIsTooltipOpen(true)}
69
- onBlur={() => setIsTooltipOpen(false)}
75
+ const triggerClassName = cn(
76
+ 'block min-w-0 flex-1 truncate font-medium text-gray-500 outline-none',
77
+ isChromeDensity ? 'text-[12px]' : 'text-[12px]',
78
+ releaseNotesLink && 'cursor-pointer rounded-sm transition-colors hover:text-gray-700 hover:underline focus-visible:ring-2 focus-visible:ring-gray-300'
79
+ );
80
+ const tooltip = releaseNotesLink
81
+ ? [
82
+ t('desktopUpdatesVersionTooltipCurrent').replace('{version}', versionLabel),
83
+ t('desktopUpdatesVersionTooltipReleaseNotes').replace('{version}', releaseNotesLink.versionLabel)
84
+ ].join('\n')
85
+ : versionLabel;
86
+ const trigger = releaseNotesLink ? (
87
+ <button
88
+ type="button"
89
+ aria-label={t('desktopUpdatesVersionReleaseNotesLabel').replace('{currentVersion}', versionLabel).replace('{releaseVersion}', releaseNotesLink.versionLabel)}
90
+ className={cn(triggerClassName, 'border-0 bg-transparent p-0 text-left')}
91
+ onClick={() => void hostCapabilityManager.openExternalUrl(releaseNotesLink.url)}
70
92
  >
71
- <span
72
- tabIndex={0}
73
- aria-label={versionLabel}
74
- className={cn(
75
- 'block min-w-0 truncate font-medium text-gray-500 outline-none',
76
- isChromeDensity ? 'text-[12px]' : 'text-[12px]',
77
- )}
78
- >
79
- {versionLabel}
80
- </span>
81
- {isTooltipOpen ? (
82
- <span className="pointer-events-none absolute left-0 top-full z-[var(--z-tooltip)] mt-1 w-max max-w-none whitespace-nowrap rounded-md border bg-popover px-3 py-1.5 text-xs font-medium text-popover-foreground shadow-md">
83
- {versionLabel}
84
- </span>
85
- ) : null}
93
+ {versionLabel}
94
+ </button>
95
+ ) : (
96
+ <span tabIndex={0} aria-label={versionLabel} className={triggerClassName}>
97
+ {versionLabel}
86
98
  </span>
87
99
  );
100
+
101
+ return (
102
+ <TooltipProvider delayDuration={250}>
103
+ <Tooltip>
104
+ <TooltipTrigger asChild>{trigger}</TooltipTrigger>
105
+ <TooltipContent side="bottom" className="max-w-72 whitespace-pre-line text-xs leading-relaxed">{tooltip}</TooltipContent>
106
+ </Tooltip>
107
+ </TooltipProvider>
108
+ );
88
109
  }
89
110
 
90
- function RuntimeUpdateInlineStatus() {
91
- const { supported, busyAction, snapshot } = useRuntimeUpdateStore();
111
+ function RuntimeUpdateInlineStatus({
112
+ supported,
113
+ busyAction,
114
+ snapshot,
115
+ }: {
116
+ supported: boolean;
117
+ busyAction: RuntimeUpdateBusyAction;
118
+ snapshot: UpdateSnapshot | null;
119
+ }) {
92
120
  if (!supported || !snapshot) {
93
121
  return null;
94
122
  }
95
- if (snapshot.status === 'downloading' || snapshot.status === 'blocked' || snapshot.status === 'failed') {
123
+ const releaseNotesLink = resolveUpdateReleaseNotesLink(snapshot);
124
+ if (snapshot.status === 'downloading') {
125
+ return (
126
+ <RuntimeUpdateInlineGroup>
127
+ <RuntimeUpdateInlineBadge snapshot={snapshot} />
128
+ {releaseNotesLink ? <RuntimeUpdateReleaseNotesButton link={releaseNotesLink} tone="amber" /> : null}
129
+ </RuntimeUpdateInlineGroup>
130
+ );
131
+ }
132
+ if (snapshot.status === 'blocked' || snapshot.status === 'failed') {
96
133
  return <RuntimeUpdateInlineBadge snapshot={snapshot} />;
97
134
  }
98
135
  if (snapshot.status === 'downloaded') {
99
136
  return (
100
- <button
101
- type="button"
102
- className="inline-flex h-5 shrink-0 items-center rounded-full bg-emerald-50 px-2 text-[11px] font-semibold leading-none text-emerald-700 ring-1 ring-emerald-100 transition-colors hover:bg-emerald-100 disabled:opacity-70"
103
- disabled={busyAction === 'applying'}
104
- onClick={() => void runtimeUpdateManager.applyDownloadedUpdate()}
105
- >
106
- {busyAction === 'applying' ? t('desktopUpdatesInlineApplying') : t('desktopUpdatesInlineReady')}
107
- </button>
137
+ <RuntimeUpdateInlineGroup>
138
+ <button
139
+ type="button"
140
+ className="inline-flex h-5 shrink-0 items-center rounded-full bg-emerald-50 px-2 text-[11px] font-semibold leading-none text-emerald-700 ring-1 ring-emerald-100 transition-colors hover:bg-emerald-100 disabled:opacity-70"
141
+ disabled={busyAction === 'applying'}
142
+ onClick={() => void runtimeUpdateManager.applyDownloadedUpdate()}
143
+ >
144
+ {busyAction === 'applying' ? t('desktopUpdatesInlineApplying') : t('desktopUpdatesInlineReady')}
145
+ </button>
146
+ {releaseNotesLink ? <RuntimeUpdateReleaseNotesButton link={releaseNotesLink} tone="emerald" /> : null}
147
+ </RuntimeUpdateInlineGroup>
108
148
  );
109
149
  }
110
150
  if (snapshot.status === 'update-available') {
111
151
  return (
112
- <button
113
- type="button"
114
- className="inline-flex h-5 shrink-0 items-center rounded-full bg-amber-50 px-2 text-[11px] font-semibold leading-none text-amber-700 ring-1 ring-amber-100 transition-colors hover:bg-amber-100 disabled:opacity-70"
115
- disabled={busyAction === 'downloading'}
116
- onClick={() => void runtimeUpdateManager.downloadUpdate()}
117
- >
118
- {busyAction === 'downloading' ? t('desktopUpdatesInlineDownloading') : t('desktopUpdatesInlineDownload')}
119
- </button>
152
+ <RuntimeUpdateInlineGroup>
153
+ <button
154
+ type="button"
155
+ className="inline-flex h-5 shrink-0 items-center rounded-full bg-amber-50 px-2 text-[11px] font-semibold leading-none text-amber-700 ring-1 ring-amber-100 transition-colors hover:bg-amber-100 disabled:opacity-70"
156
+ disabled={busyAction === 'downloading'}
157
+ onClick={() => void runtimeUpdateManager.downloadUpdate()}
158
+ >
159
+ {busyAction === 'downloading' ? t('desktopUpdatesInlineDownloading') : t('desktopUpdatesInlineDownload')}
160
+ </button>
161
+ {releaseNotesLink ? <RuntimeUpdateReleaseNotesButton link={releaseNotesLink} tone="amber" /> : null}
162
+ </RuntimeUpdateInlineGroup>
120
163
  );
121
164
  }
122
165
  return null;
123
166
  }
124
167
 
168
+ function RuntimeUpdateInlineGroup({ children }: { children: ReactNode }) {
169
+ return <span className="inline-flex shrink-0 items-center gap-1">{children}</span>;
170
+ }
171
+
172
+ function RuntimeUpdateReleaseNotesButton({
173
+ link,
174
+ tone,
175
+ }: {
176
+ link: ReleaseNotesLink;
177
+ tone: 'amber' | 'emerald';
178
+ }) {
179
+ const label = t('desktopUpdatesUpdateReleaseNotesLabel').replace('{version}', link.versionLabel);
180
+ const className = cn(
181
+ 'inline-flex h-5 w-5 shrink-0 items-center justify-center rounded-full text-[11px] ring-1 transition-colors focus-visible:outline-none focus-visible:ring-2',
182
+ tone === 'emerald'
183
+ ? 'bg-emerald-50 text-emerald-700 ring-emerald-100 hover:bg-emerald-100 focus-visible:ring-emerald-300'
184
+ : 'bg-amber-50 text-amber-700 ring-amber-100 hover:bg-amber-100 focus-visible:ring-amber-300'
185
+ );
186
+ return (
187
+ <TooltipProvider delayDuration={250}>
188
+ <Tooltip>
189
+ <TooltipTrigger asChild>
190
+ <button
191
+ type="button"
192
+ aria-label={label}
193
+ className={className}
194
+ onClick={() => void hostCapabilityManager.openExternalUrl(link.url)}
195
+ >
196
+ <ExternalLink className="h-3 w-3" aria-hidden="true" />
197
+ </button>
198
+ </TooltipTrigger>
199
+ <TooltipContent side="bottom" className="text-xs">{label}</TooltipContent>
200
+ </Tooltip>
201
+ </TooltipProvider>
202
+ );
203
+ }
204
+
125
205
  function RuntimeUpdateInlineBadge({ snapshot }: { snapshot: UpdateSnapshot }) {
126
206
  if (snapshot.status === 'blocked' || snapshot.status === 'failed') {
127
207
  return <RuntimeUpdateIssueIcon snapshot={snapshot} />;
@@ -149,15 +229,21 @@ function RuntimeUpdateIssueIcon({ snapshot }: { snapshot: UpdateSnapshot }) {
149
229
  : null;
150
230
  const tooltip = [title, rootCause, diagnostic, recoveryCommand].filter(Boolean).join('\n');
151
231
  return (
152
- <span
153
- role="img"
154
- aria-label={title}
155
- title={tooltip}
156
- tabIndex={0}
157
- className="inline-flex h-5 w-5 shrink-0 items-center justify-center rounded-full bg-amber-50 text-[13px] font-bold leading-none text-amber-700 ring-1 ring-amber-100"
158
- >
159
- !
160
- </span>
232
+ <TooltipProvider delayDuration={250}>
233
+ <Tooltip>
234
+ <TooltipTrigger asChild>
235
+ <span
236
+ role="img"
237
+ aria-label={title}
238
+ tabIndex={0}
239
+ className="inline-flex h-5 w-5 shrink-0 items-center justify-center rounded-full bg-amber-50 text-[13px] font-bold leading-none text-amber-700 ring-1 ring-amber-100 outline-none focus-visible:ring-2 focus-visible:ring-amber-300"
240
+ >
241
+ !
242
+ </span>
243
+ </TooltipTrigger>
244
+ <TooltipContent side="bottom" className="max-w-80 whitespace-pre-line break-words text-xs leading-relaxed">{tooltip}</TooltipContent>
245
+ </Tooltip>
246
+ </TooltipProvider>
161
247
  );
162
248
  }
163
249
 
@@ -5,6 +5,9 @@ import { setLanguage } from '@/shared/lib/i18n';
5
5
 
6
6
  const mocks = vi.hoisted(() => ({
7
7
  useServerPathBrowse: vi.fn(),
8
+ useServerPathCreateDirectory: vi.fn(),
9
+ createDirectory: vi.fn(),
10
+ resetCreateDirectory: vi.fn(),
8
11
  refetch: vi.fn(),
9
12
  }));
10
13
 
@@ -12,21 +15,75 @@ vi.mock('@/shared/hooks/use-server-path-browse', () => ({
12
15
  useServerPathBrowse: mocks.useServerPathBrowse,
13
16
  }));
14
17
 
18
+ vi.mock('@/shared/hooks/use-server-path-create-directory', () => ({
19
+ useServerPathCreateDirectory: mocks.useServerPathCreateDirectory,
20
+ }));
21
+
22
+ type PickerOverrides = {
23
+ currentPath?: string;
24
+ defaultWorkspacePath?: string;
25
+ description?: string;
26
+ open?: boolean;
27
+ };
28
+
29
+ const createPickerElement = (overrides?: PickerOverrides) => (
30
+ <ServerPathPickerDialog
31
+ open={overrides?.open ?? true}
32
+ currentPath={overrides?.currentPath ?? '/workspace'}
33
+ defaultWorkspacePath={overrides?.defaultWorkspacePath}
34
+ isSaving={false}
35
+ onOpenChange={() => {}}
36
+ onConfirm={() => {}}
37
+ title="选择目录"
38
+ description={overrides?.description ?? '选择一个目录'}
39
+ pathLabel="目录"
40
+ confirmLabel="确定"
41
+ />
42
+ );
43
+
44
+ const renderPicker = (overrides?: PickerOverrides) => render(createPickerElement(overrides));
45
+
15
46
  describe('ServerPathPickerDialog', () => {
16
47
  beforeEach(() => {
17
48
  setLanguage('zh');
18
49
  mocks.refetch.mockReset();
50
+ mocks.refetch.mockResolvedValue(undefined);
51
+ mocks.createDirectory.mockReset();
52
+ mocks.createDirectory.mockResolvedValue({ path: '/workspace/new-folder' });
53
+ mocks.resetCreateDirectory.mockReset();
54
+ mocks.useServerPathCreateDirectory.mockReturnValue({
55
+ mutateAsync: mocks.createDirectory,
56
+ reset: mocks.resetCreateDirectory,
57
+ isPending: false,
58
+ error: null,
59
+ });
19
60
  mocks.useServerPathBrowse.mockReturnValue({
20
61
  data: {
21
62
  currentPath: '/workspace',
22
63
  homePath: '/Users/peiwang',
23
64
  parentPath: '/',
24
- breadcrumbs: [
25
- { label: 'workspace', path: '/workspace' },
26
- ],
65
+ breadcrumbs: [{ label: 'workspace', path: '/workspace' }],
27
66
  entries: [
28
- { name: 'playground', path: '/workspace/playground' },
29
- { name: 'nextbot', path: '/workspace/nextbot' },
67
+ {
68
+ name: 'playground',
69
+ path: '/workspace/playground',
70
+ kind: 'directory',
71
+ hidden: false,
72
+ },
73
+ {
74
+ name: 'nextbot',
75
+ path: '/workspace/nextbot',
76
+ kind: 'directory',
77
+ hidden: false,
78
+ },
79
+ ],
80
+ locations: [
81
+ { kind: 'desktop', path: '/Users/peiwang/Desktop' },
82
+ { kind: 'documents', path: '/Users/peiwang/Documents' },
83
+ { kind: 'downloads', path: '/Users/peiwang/Downloads' },
84
+ { kind: 'icloud-drive', path: '/Users/peiwang/Library/Mobile Documents/com~apple~CloudDocs' },
85
+ { kind: 'applications', path: '/Applications' },
86
+ { kind: 'volumes', path: '/Volumes' },
30
87
  ],
31
88
  },
32
89
  isLoading: false,
@@ -37,55 +94,248 @@ describe('ServerPathPickerDialog', () => {
37
94
 
38
95
  it('filters entries inside the current directory', async () => {
39
96
  const user = userEvent.setup();
40
-
41
- render(
42
- <ServerPathPickerDialog
43
- open
44
- currentPath="/workspace"
45
- isSaving={false}
46
- onOpenChange={() => {}}
47
- onConfirm={() => {}}
48
- title="选择目录"
49
- description="选择一个目录"
50
- pathLabel="目录"
51
- confirmLabel="确定"
52
- />
53
- );
97
+ renderPicker();
54
98
 
55
99
  expect(screen.getByText('playground')).toBeTruthy();
56
100
  expect(screen.getByText('nextbot')).toBeTruthy();
57
101
 
58
- await user.type(screen.getByPlaceholderText('搜索当前目录'), 'play');
102
+ await user.type(screen.getAllByPlaceholderText('搜索当前目录')[0], 'play');
59
103
 
60
104
  expect(screen.getByText('playground')).toBeTruthy();
61
105
  expect(screen.queryByText('nextbot')).toBeNull();
62
106
  });
63
107
 
64
- it('shows the empty-search hint and keeps the list region scroll-contained', async () => {
108
+ it('shows server-backed Mac locations and opens sidebar locations with one click', async () => {
65
109
  const user = userEvent.setup();
66
- render(
67
- <ServerPathPickerDialog
68
- open
69
- currentPath="/workspace"
70
- isSaving={false}
71
- onOpenChange={() => {}}
72
- onConfirm={() => {}}
73
- title="选择目录"
74
- description="选择一个目录"
75
- pathLabel="目录"
76
- confirmLabel="确定"
77
- />
110
+ renderPicker();
111
+
112
+ expect(screen.getByRole('button', { name: '桌面' })).toBeTruthy();
113
+ expect(screen.getByRole('button', { name: '文稿' })).toBeTruthy();
114
+ expect(screen.getByRole('button', { name: '下载' })).toBeTruthy();
115
+ expect(screen.getByRole('button', { name: 'iCloud 云盘' })).toBeTruthy();
116
+ expect(screen.getByRole('button', { name: '应用程序' })).toBeTruthy();
117
+ expect(screen.getByRole('button', { name: '磁盘与卷' })).toBeTruthy();
118
+
119
+ await user.click(screen.getByRole('button', { name: '下载' }));
120
+
121
+ expect(mocks.useServerPathBrowse).toHaveBeenLastCalledWith({
122
+ path: '/Users/peiwang/Downloads',
123
+ enabled: true,
124
+ });
125
+ });
126
+
127
+ it('keeps the explicit current path when an older home browse result is still visible', () => {
128
+ mocks.useServerPathBrowse.mockReturnValue({
129
+ data: {
130
+ currentPath: '/Users/peiwang',
131
+ homePath: '/Users/peiwang',
132
+ parentPath: '/Users',
133
+ breadcrumbs: [
134
+ { label: 'Users', path: '/Users' },
135
+ { label: 'peiwang', path: '/Users/peiwang' },
136
+ ],
137
+ entries: [],
138
+ },
139
+ isLoading: false,
140
+ error: null,
141
+ refetch: mocks.refetch,
142
+ });
143
+
144
+ renderPicker({ currentPath: '/Users/peiwang/.nextclaw/workspace' });
145
+
146
+ expect((screen.getByLabelText('目录') as HTMLInputElement).value).toBe(
147
+ '/Users/peiwang/.nextclaw/workspace',
78
148
  );
149
+ expect(screen.getByText('/Users/peiwang/.nextclaw/workspace')).toBeTruthy();
150
+ });
151
+
152
+ it('starts at the workdir and keeps the canonical address and selected folder aligned', () => {
153
+ mocks.useServerPathBrowse.mockReturnValue({
154
+ data: {
155
+ currentPath: '/Users/peiwang/.nextclaw/workspace',
156
+ homePath: '/Users/peiwang',
157
+ parentPath: '/Users/peiwang/.nextclaw',
158
+ breadcrumbs: [
159
+ { label: '/', path: '/' },
160
+ { label: 'Users', path: '/Users' },
161
+ { label: 'peiwang', path: '/Users/peiwang' },
162
+ { label: '.nextclaw', path: '/Users/peiwang/.nextclaw' },
163
+ { label: 'workspace', path: '/Users/peiwang/.nextclaw/workspace' },
164
+ ],
165
+ entries: [],
166
+ },
167
+ isLoading: false,
168
+ error: null,
169
+ refetch: mocks.refetch,
170
+ });
79
171
 
80
- await user.type(screen.getByPlaceholderText('搜索当前目录'), 'missing');
172
+ renderPicker({
173
+ currentPath: '',
174
+ defaultWorkspacePath: '~/.nextclaw/workspace',
175
+ description: undefined,
176
+ });
81
177
 
82
- expect(screen.getByText('当前目录下没有匹配结果。')).toBeTruthy();
178
+ expect(mocks.useServerPathBrowse).toHaveBeenLastCalledWith({
179
+ path: '~/.nextclaw/workspace',
180
+ enabled: true,
181
+ });
182
+ expect((screen.getByLabelText('目录') as HTMLInputElement).value).toBe(
183
+ '/Users/peiwang/.nextclaw/workspace',
184
+ );
185
+ expect(screen.getByRole('button', { name: 'NextClaw 工作区' })).toBeTruthy();
186
+ expect(screen.getByText('workspace')).toBeTruthy();
187
+ });
188
+
189
+ it('normalizes a home-relative workdir with Windows separators', () => {
190
+ mocks.useServerPathBrowse.mockReturnValue({
191
+ data: {
192
+ currentPath: 'C:\\Users\\alice\\.nextclaw\\workspace',
193
+ homePath: 'C:\\Users\\alice',
194
+ parentPath: 'C:\\Users\\alice\\.nextclaw',
195
+ breadcrumbs: [
196
+ { label: 'C:', path: 'C:\\' },
197
+ { label: 'Users', path: 'C:\\Users' },
198
+ { label: 'alice', path: 'C:\\Users\\alice' },
199
+ { label: '.nextclaw', path: 'C:\\Users\\alice\\.nextclaw' },
200
+ { label: 'workspace', path: 'C:\\Users\\alice\\.nextclaw\\workspace' },
201
+ ],
202
+ entries: [],
203
+ locations: [],
204
+ },
205
+ isLoading: false,
206
+ error: null,
207
+ refetch: mocks.refetch,
208
+ });
209
+
210
+ renderPicker({ currentPath: '', defaultWorkspacePath: '~/.nextclaw/workspace' });
211
+
212
+ expect((screen.getByLabelText('目录') as HTMLInputElement).value).toBe(
213
+ 'C:\\Users\\alice\\.nextclaw\\workspace',
214
+ );
215
+ expect(screen.getByText('workspace')).toBeTruthy();
216
+ });
217
+
218
+ it('creates a folder in the current directory and selects it without entering it', async () => {
219
+ const user = userEvent.setup();
220
+ renderPicker();
221
+
222
+ await user.click(screen.getByRole('button', { name: '新建文件夹' }));
223
+ await user.type(screen.getByLabelText('文件夹名称'), 'new-folder');
224
+ await user.click(screen.getByRole('button', { name: '创建' }));
225
+
226
+ expect(mocks.createDirectory).toHaveBeenCalledWith({
227
+ parentPath: '/workspace',
228
+ name: 'new-folder',
229
+ });
230
+ expect(mocks.refetch).toHaveBeenCalledOnce();
231
+ expect((screen.getByLabelText('目录') as HTMLInputElement).value).toBe(
232
+ '/workspace/new-folder',
233
+ );
234
+ expect((screen.getByRole('button', { name: '后退' }) as HTMLButtonElement).disabled).toBe(
235
+ true,
236
+ );
237
+ });
238
+
239
+ it('uses single click to select and double click to enter a folder', async () => {
240
+ const user = userEvent.setup();
241
+ renderPicker();
242
+ const playground = screen.getByRole('option', { name: /playground/ });
243
+
244
+ expect(screen.getByText('单击选择,双击进入')).toBeTruthy();
245
+
246
+ await user.click(playground);
247
+
248
+ expect((screen.getByLabelText('目录') as HTMLInputElement).value).toBe(
249
+ '/workspace/playground',
250
+ );
251
+ expect(mocks.useServerPathBrowse).toHaveBeenLastCalledWith({
252
+ path: '/workspace',
253
+ enabled: true,
254
+ });
255
+
256
+ await user.dblClick(playground);
257
+
258
+ expect(mocks.useServerPathBrowse).toHaveBeenLastCalledWith({
259
+ path: '/workspace/playground',
260
+ enabled: true,
261
+ });
262
+ expect((screen.getByRole('button', { name: '后退' }) as HTMLButtonElement).disabled).toBe(
263
+ false,
264
+ );
265
+ });
266
+
267
+ it('starts a fresh selection session when the dialog is reopened', async () => {
268
+ const user = userEvent.setup();
269
+ const view = renderPicker();
83
270
 
271
+ await user.click(screen.getByRole('option', { name: /playground/ }));
272
+ expect((screen.getByLabelText('目录') as HTMLInputElement).value).toBe(
273
+ '/workspace/playground',
274
+ );
275
+
276
+ view.rerender(createPickerElement({ open: false }));
277
+ view.rerender(createPickerElement());
278
+
279
+ expect((screen.getByLabelText('目录') as HTMLInputElement).value).toBe('/workspace');
280
+ expect((screen.getByRole('button', { name: '后退' }) as HTMLButtonElement).disabled).toBe(
281
+ true,
282
+ );
283
+ });
284
+
285
+ it('opens an editable address and records navigation history', async () => {
286
+ const user = userEvent.setup();
287
+ renderPicker();
288
+
289
+ await user.click(screen.getByRole('button', { name: '编辑地址' }));
290
+ const addressInput = screen.getByLabelText('地址');
291
+ await user.clear(addressInput);
292
+ await user.type(addressInput, '/workspace/playground{Enter}');
293
+
294
+ expect((screen.getByLabelText('目录') as HTMLInputElement).value).toBe(
295
+ '/workspace/playground',
296
+ );
297
+ expect((screen.getByRole('button', { name: '后退' }) as HTMLButtonElement).disabled).toBe(
298
+ false,
299
+ );
300
+
301
+ await user.click(screen.getByRole('button', { name: '后退' }));
302
+ expect((screen.getByLabelText('目录') as HTMLInputElement).value).toBe('/workspace');
303
+ expect((screen.getByRole('button', { name: '前进' }) as HTMLButtonElement).disabled).toBe(
304
+ false,
305
+ );
306
+ });
307
+
308
+ it('supports arrow selection and Enter navigation from the directory list', async () => {
309
+ const user = userEvent.setup();
310
+ renderPicker();
311
+ const playground = screen.getByRole('option', { name: /playground/ });
312
+
313
+ await user.click(playground);
314
+ await user.keyboard('{ArrowDown}');
315
+
316
+ expect((screen.getByLabelText('目录') as HTMLInputElement).value).toBe(
317
+ '/workspace/nextbot',
318
+ );
319
+ await user.keyboard('{Enter}');
320
+ expect(mocks.useServerPathBrowse).toHaveBeenLastCalledWith({
321
+ path: '/workspace/nextbot',
322
+ enabled: true,
323
+ });
324
+ });
325
+
326
+ it('shows the empty-search hint and keeps the list region scroll-contained', async () => {
327
+ const user = userEvent.setup();
328
+ renderPicker();
329
+
330
+ await user.type(screen.getAllByPlaceholderText('搜索当前目录')[0], 'missing');
331
+
332
+ expect(screen.getByText('当前目录下没有匹配结果。')).toBeTruthy();
84
333
  const dialog = screen.getByRole('dialog');
85
334
  expect(dialog.className).toContain('overflow-hidden');
86
- expect(dialog.className).toContain('sm:h-[42rem]');
87
-
88
- const scrollArea = screen.getByText('当前目录下没有匹配结果。').closest('.overflow-auto');
335
+ expect(dialog.className).toContain('sm:h-[44rem]');
336
+ const scrollArea = screen
337
+ .getByText('当前目录下没有匹配结果。')
338
+ .closest('.overflow-auto');
89
339
  expect(scrollArea?.className).toContain('flex-1');
90
340
  expect(scrollArea?.className).toContain('min-h-0');
91
341
  });