@nextclaw/ui 0.15.24 → 0.15.26

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (312) hide show
  1. package/CHANGELOG.md +39 -0
  2. package/dist/assets/{abnfDiagram-VRR7QNED-DvTjgVMf.js → abnfDiagram-VRR7QNED-BcFJp_1X.js} +1 -1
  3. package/dist/assets/{aiden0z-pptx-renderer.es-C1nfBAXR.js → aiden0z-pptx-renderer.es-Dp2ezlQn.js} +1 -1
  4. package/dist/assets/{api-BxPNDp30.js → api-CK3Fro7z.js} +6 -6
  5. package/dist/assets/appearance-settings-page-zqwWO8Dy.js +1 -0
  6. package/dist/assets/{arc-BvhODBXv.js → arc-CkgyPMyW.js} +1 -1
  7. package/dist/assets/architecture-TIHT7OUA-DO2x53FK.js +1 -0
  8. package/dist/assets/{architectureDiagram-ZJ3FMSHR-BKO9RgNS.js → architectureDiagram-ZJ3FMSHR-CMd4cIjI.js} +1 -1
  9. package/dist/assets/{blockDiagram-677ZJIJ3-DHpDXy7D.js → blockDiagram-677ZJIJ3-Bz3tbhet.js} +1 -1
  10. package/dist/assets/{c4Diagram-LMCZKHZV-BG_iQnDr.js → c4Diagram-LMCZKHZV-D9hFNAGv.js} +1 -1
  11. package/dist/assets/channel-nBvMaVyP.js +1 -0
  12. package/dist/assets/{channels-list-page-DRaNWAxC.js → channels-list-page-CVCZBlCL.js} +2 -2
  13. package/dist/assets/chat-page-D4wehRTz.js +1 -0
  14. package/dist/assets/chunk-2Q5K7J3B-CJgqFDS4.js +1 -0
  15. package/dist/assets/{chunk-32BRIVSS-DR9M0LLx.js → chunk-32BRIVSS-7NrjEITq.js} +1 -1
  16. package/dist/assets/{chunk-52WLFC77-698iC3BB.js → chunk-52WLFC77-viIcqj9q.js} +1 -1
  17. package/dist/assets/{chunk-5FCAYU7R-DikTj9Ey.js → chunk-5FCAYU7R-Vsu4J-VM.js} +1 -1
  18. package/dist/assets/{chunk-5HE753X5-ko0o7_q0.js → chunk-5HE753X5-C-w6CQZK.js} +1 -1
  19. package/dist/assets/{chunk-5JV3BV7I-CEHufhgo.js → chunk-5JV3BV7I-CuKgJQ4W.js} +1 -1
  20. package/dist/assets/{chunk-5TONJI2A-CGgno1cu.js → chunk-5TONJI2A-CYeiWZKb.js} +1 -1
  21. package/dist/assets/{chunk-5VM5RSS4-DkiB4cUQ.js → chunk-5VM5RSS4-DRCbfwIn.js} +1 -1
  22. package/dist/assets/{chunk-7BUUIJ7U-DzptcytQ.js → chunk-7BUUIJ7U-BvydOcm3.js} +1 -1
  23. package/dist/assets/{chunk-BIQX33UG-DMRSQJvT.js → chunk-BIQX33UG-RlppOXKo.js} +1 -1
  24. package/dist/assets/{chunk-C7G6YPKG-Bw1BwwuY.js → chunk-C7G6YPKG-DAizzYwr.js} +1 -1
  25. package/dist/assets/{chunk-CQNSW5MT-C0z6eHBk.js → chunk-CQNSW5MT-5eChJHca.js} +1 -1
  26. package/dist/assets/{chunk-CYSBUYHQ-ByPyAouO.js → chunk-CYSBUYHQ-YHPr53fv.js} +1 -1
  27. package/dist/assets/{chunk-EMLP6XTP-CJlAXWZg.js → chunk-EMLP6XTP-CK4D7Hz5.js} +1 -1
  28. package/dist/assets/{chunk-EX3LRPZG-Dqq4zKJu.js → chunk-EX3LRPZG-BmlDFIRW.js} +1 -1
  29. package/dist/assets/{chunk-FWX5IMBZ-B0SNoCmi.js → chunk-FWX5IMBZ-D0ksHZZ9.js} +2 -2
  30. package/dist/assets/{chunk-HOUHSVGY-DG_GJih_.js → chunk-HOUHSVGY-DPkq8FCm.js} +1 -1
  31. package/dist/assets/{chunk-ICXQ74PX-Drxk_6bE.js → chunk-ICXQ74PX-FAatv5fC.js} +1 -1
  32. package/dist/assets/{chunk-JG7HCLWE-DdLFPpnd.js → chunk-JG7HCLWE-DLkIi4F7.js} +1 -1
  33. package/dist/assets/{chunk-JWPE2WC7-B8M4G76F.js → chunk-JWPE2WC7-DawOjNE3.js} +1 -1
  34. package/dist/assets/{chunk-MOJQB5TN-XP6FJHY_.js → chunk-MOJQB5TN-Di52TorW.js} +1 -1
  35. package/dist/assets/{chunk-MOZMSUNE-C_6x18Qw.js → chunk-MOZMSUNE-PfyK9w1-.js} +1 -1
  36. package/dist/assets/{chunk-OGEWGWER-N25BzCmG.js → chunk-OGEWGWER-C3jdH3Ty.js} +1 -1
  37. package/dist/assets/{chunk-OSBZ3O6U-ASDEm7y0.js → chunk-OSBZ3O6U-CecHo-f0.js} +1 -1
  38. package/dist/assets/{chunk-PUDLZKDR-CTlvAcn7.js → chunk-PUDLZKDR-f3IJh3sN.js} +1 -1
  39. package/dist/assets/{chunk-Q4XR5HBZ-Da9BWfB6.js → chunk-Q4XR5HBZ-DQFeUPxZ.js} +1 -1
  40. package/dist/assets/{chunk-QBLGF6JB-Bi6RpTJO.js → chunk-QBLGF6JB-BhUeG0y2.js} +1 -1
  41. package/dist/assets/{chunk-R7FJI6CG-DkMsSPvK.js → chunk-R7FJI6CG-C3XpZ-2u.js} +1 -1
  42. package/dist/assets/{chunk-RYQCIY6F-DZp0eU-j.js → chunk-RYQCIY6F-CAqa5klQ.js} +1 -1
  43. package/dist/assets/{chunk-U6XO7XAA-vfinDSrj.js → chunk-U6XO7XAA-D_WXezeD.js} +1 -1
  44. package/dist/assets/{chunk-V7JOEXUC-C9Nksccs.js → chunk-V7JOEXUC-CZ70oOtp.js} +1 -1
  45. package/dist/assets/chunk-VAUOI2AC-BGvh3yIx.js +1 -0
  46. package/dist/assets/{chunk-VR4S4FIN-Xv6zfwXs.js → chunk-VR4S4FIN-B7AHEYYk.js} +1 -1
  47. package/dist/assets/{chunk-WYO6CB5R-mOJNzbWl.js → chunk-WYO6CB5R-Dn9To_OI.js} +2 -2
  48. package/dist/assets/chunk-XXDRQBXY-DaBxG5gs.js +1 -0
  49. package/dist/assets/{chunk-YOTPTUD7-1-aKSu4o.js → chunk-YOTPTUD7-BYEgRBk3.js} +1 -1
  50. package/dist/assets/{chunk-ZGVPDNZ5-OJ8veMpK.js → chunk-ZGVPDNZ5-C6YtZDU0.js} +1 -1
  51. package/dist/assets/{chunk-ZIRB5QZD-SR_Nq_PP.js → chunk-ZIRB5QZD-DMuYtnHF.js} +1 -1
  52. package/dist/assets/classDiagram-OUVF2IWQ-P6N03G2R.js +1 -0
  53. package/dist/assets/classDiagram-v2-EOCWNBFH-B1_uLvMv.js +1 -0
  54. package/dist/assets/config-split-page-CceNFvlY.js +1 -0
  55. package/dist/assets/confirm-dialog-CWeNYWZY.js +5 -0
  56. package/dist/assets/{cose-bilkent-JH36ORCC-NjVgz2Bw.js → cose-bilkent-JH36ORCC-Bc2Ex5Y3.js} +1 -1
  57. package/dist/assets/cynefin-VYW2F7L2-B-H8Qp-n.js +1 -0
  58. package/dist/assets/{cynefinDiagram-TSTJHNR4-B_17LDqD.js → cynefinDiagram-TSTJHNR4-DVysQoGz.js} +1 -1
  59. package/dist/assets/{dagre-CMe101gk.js → dagre-CMhQ1cAG.js} +1 -1
  60. package/dist/assets/{dagre-VKFMJZFB-qo8nLb3B.js → dagre-VKFMJZFB-RYyWpPJZ.js} +1 -1
  61. package/dist/assets/desktop-update-config-Dirc3bpt.js +1 -0
  62. package/dist/assets/{diagram-FQU43EPY-Ba3XIklC.js → diagram-FQU43EPY-BD6KONdF.js} +1 -1
  63. package/dist/assets/{diagram-G47NLZAW-CG45TcUx.js → diagram-G47NLZAW-CGB2ptRR.js} +1 -1
  64. package/dist/assets/{diagram-NH7WQ7WH-2F2HrwV-.js → diagram-NH7WQ7WH-DnkOI6cU.js} +1 -1
  65. package/dist/assets/{diagram-OA4YK3LP-CXfWH9NZ.js → diagram-OA4YK3LP-DF82RZ7P.js} +1 -1
  66. package/dist/assets/{diagram-WEI45ONY-CH_4yepc.js → diagram-WEI45ONY-Be2v7Ck9.js} +1 -1
  67. package/dist/assets/{dist-B4Pf5X3Q.js → dist-AYxkFx2B.js} +1 -1
  68. package/dist/assets/{dist-DHEWkGWX.js → dist-D9xtGgdL.js} +1 -1
  69. package/dist/assets/doc-browser-4Sud6dWw.js +1 -0
  70. package/dist/assets/doc-browser-D4x_yH5F.js +1 -0
  71. package/dist/assets/doc-browser-context-Cb7Z0j90.js +1 -0
  72. package/dist/assets/{docx-preview-CoM4JK7_.js → docx-preview-BqwAjrj3.js} +1 -1
  73. package/dist/assets/{ebnfDiagram-CCIWWBDH-BwiG4Ea1.js → ebnfDiagram-CCIWWBDH-CP98OYk8.js} +1 -1
  74. package/dist/assets/{erDiagram-Q63AITRT-BML3hpnQ.js → erDiagram-Q63AITRT-ZLlx8xfs.js} +1 -1
  75. package/dist/assets/eventmodeling-45OFAUF4-YVtShyFA.js +1 -0
  76. package/dist/assets/flowDiagram-23GEKE2U-BeKXabVO.js +1 -0
  77. package/dist/assets/form-actions-B2q4jgjE.js +1 -0
  78. package/dist/assets/{ganttDiagram-NO4QXBWP-80enfWbD.js → ganttDiagram-NO4QXBWP-DXCDjHR2.js} +1 -1
  79. package/dist/assets/gitGraph-TEB2WS4Q-jTSiBj-p.js +1 -0
  80. package/dist/assets/{gitGraphDiagram-IHSO6WYX-CVLqXr-b.js → gitGraphDiagram-IHSO6WYX-l2HWrTJp.js} +1 -1
  81. package/dist/assets/index-CdyTsSBu.css +1 -0
  82. package/dist/assets/index-Das_bw2h.js +107 -0
  83. package/dist/assets/info-DKCQHKI2-BIporNWb.js +1 -0
  84. package/dist/assets/{infoDiagram-FWYZ7A6U-Dyvs2t0i.js → infoDiagram-FWYZ7A6U-D-DjX-bn.js} +1 -1
  85. package/dist/assets/{ishikawaDiagram-FXEZZL3T-sSnxDKfh.js → ishikawaDiagram-FXEZZL3T-y_Xy128F.js} +1 -1
  86. package/dist/assets/{journeyDiagram-5HDEW3XC-D22V71Mf.js → journeyDiagram-5HDEW3XC-DJn5xYcQ.js} +1 -1
  87. package/dist/assets/{kanban-definition-HUTT4EX6-D3ds0p-g.js → kanban-definition-HUTT4EX6-DJVIOR21.js} +1 -1
  88. package/dist/assets/{line-CbyFs065.js → line-Cos7PIOj.js} +1 -1
  89. package/dist/assets/{linear-4DKtnZBS.js → linear-BtDLqtzF.js} +1 -1
  90. package/dist/assets/loader-circle-CpmBdaon.js +1 -0
  91. package/dist/assets/{map-CcZzEoz6.js → map-BKnQyvd0.js} +1 -1
  92. package/dist/assets/mcp-marketplace-page-CdOY3Sop.js +1 -0
  93. package/dist/assets/mcp-marketplace-page-DvjrfqEt.js +9 -0
  94. package/dist/assets/{mermaid-parser.core-DPmMOjXD.js → mermaid-parser.core-XUNZhr2W.js} +2 -2
  95. package/dist/assets/{mermaid.core-BSf-ddRa.js → mermaid.core-ySrvolIe.js} +3 -3
  96. package/dist/assets/{mindmap-definition-LN4V7U3C-BUTwWPyx.js → mindmap-definition-LN4V7U3C-BypL9tei.js} +1 -1
  97. package/dist/assets/model-config-page-DfOLyQqR.js +1 -0
  98. package/dist/assets/{navigation-link-CAFOcXNg.js → navigation-link-MiiyoIjH.js} +2 -2
  99. package/dist/assets/{ordinal-BgLYtOZE.js → ordinal-CxxVWxYU.js} +1 -1
  100. package/dist/assets/packet-7NZHBO7P-CuFMADV0.js +1 -0
  101. package/dist/assets/{pegDiagram-2B236MQR-BEu_ZBjc.js → pegDiagram-2B236MQR-D-U2KBGv.js} +1 -1
  102. package/dist/assets/pie-RZYD4A2V-XUeIXhM9.js +1 -0
  103. package/dist/assets/{pieDiagram-ENE6RG2P-CAwAwuBd.js → pieDiagram-ENE6RG2P-CBi3GusJ.js} +1 -1
  104. package/dist/assets/plus-C8FkjpSZ.js +1 -0
  105. package/dist/assets/provider-scoped-model-input-DXzjr5l0.js +1 -0
  106. package/dist/assets/providers-config-page-CaO8WfZH.js +1 -0
  107. package/dist/assets/{quadrantDiagram-ABIIQ3AL-CwE6F-_2.js → quadrantDiagram-ABIIQ3AL-H0ZuvnZL.js} +1 -1
  108. package/dist/assets/radar-I7S5WNFK-D-NNGh1k.js +1 -0
  109. package/dist/assets/railroad-3IZDKUUU-C67_uDaM.js +1 -0
  110. package/dist/assets/railroad-abnf-AHOZXSZD-BZ8Yflnd.js +1 -0
  111. package/dist/assets/railroad-ebnf-EBAXGLYW-B7zO7RoK.js +1 -0
  112. package/dist/assets/railroad-peg-LSFZ7HO6-R63PTgLz.js +1 -0
  113. package/dist/assets/{railroadDiagram-RFXS5EU6-xgLekU3B.js → railroadDiagram-RFXS5EU6-D7Y2EqSd.js} +1 -1
  114. package/dist/assets/react-C8XFFvuz.js +8 -0
  115. package/dist/assets/{refresh-cw-CWDp_JnN.js → refresh-cw-a2VVeeAF.js} +1 -1
  116. package/dist/assets/remote-BJHXa4mB.js +1 -0
  117. package/dist/assets/{requirementDiagram-TGXJPOKE-CyVUzEX5.js → requirementDiagram-TGXJPOKE-BiXNX1NN.js} +1 -1
  118. package/dist/assets/rotate-cw-DJ7UF3F2.js +1 -0
  119. package/dist/assets/runtime-config-page-BuA4A02z.js +1 -0
  120. package/dist/assets/{sankeyDiagram-HTMAVEWB-JK6bFmYB.js → sankeyDiagram-HTMAVEWB-qzDeLztW.js} +1 -1
  121. package/dist/assets/{save-Cnr71JON.js → save-CGm23Yww.js} +1 -1
  122. package/dist/assets/{search-CCX11X2m.js → search-CH2t--sH.js} +1 -1
  123. package/dist/assets/search-config-page-BAmYqoAz.js +1 -0
  124. package/dist/assets/secrets-config-page-BlPOuql_.js +3 -0
  125. package/dist/assets/security-config-CbDiY2xr.js +1 -0
  126. package/dist/assets/{select-DvwBu99K.js → select-DnxxDpcV.js} +2 -2
  127. package/dist/assets/{sequenceDiagram-DBY2YBRQ-BR5IokXV.js → sequenceDiagram-DBY2YBRQ-BtRbPQtL.js} +1 -1
  128. package/dist/assets/{settings-2-Dmv6zMne.js → settings-2-agnMYc8a.js} +1 -1
  129. package/dist/assets/{sizeCapture-X5ZJPWSS-SxG_YnMA.js → sizeCapture-X5ZJPWSS-CMYvM3SL.js} +1 -1
  130. package/dist/assets/skeleton-XpqeWL62.js +1 -0
  131. package/dist/assets/{src-vgz8E_-O.js → src-Bw-mzbQb.js} +1 -1
  132. package/dist/assets/{stateDiagram-2N3HPSRC-DD4OoYhn.js → stateDiagram-2N3HPSRC-BJ-mFeOz.js} +1 -1
  133. package/dist/assets/stateDiagram-v2-6OUMAXLB-BE-HqxOK.js +1 -0
  134. package/dist/assets/{swimlanes-5IMT3BWC-BWkFeyCT.js → swimlanes-5IMT3BWC-BsucVOO2.js} +2 -2
  135. package/dist/assets/swimlanesDiagram-G3AALYLV-Dr1H9LJH.js +8 -0
  136. package/dist/assets/{tag-chip-gMPvx7n_.js → tag-chip-CPOc8b2N.js} +1 -1
  137. package/dist/assets/{timeline-definition-FHXFAJF6-VTRPy6KA.js → timeline-definition-FHXFAJF6-Bicewlzo.js} +1 -1
  138. package/dist/assets/treeView-QDETBFTQ-npJ5dbqS.js +1 -0
  139. package/dist/assets/treemap-6X3UGDF4-pYrBx6oa.js +1 -0
  140. package/dist/assets/{vennDiagram-L72KCM5P-Bvk42Pek.js → vennDiagram-L72KCM5P-BxOOnO9x.js} +1 -1
  141. package/dist/assets/wardley-OPB4EBWU-CmSRKLR8.js +1 -0
  142. package/dist/assets/{wardleyDiagram-EHGQE667-DOFjx2s3.js → wardleyDiagram-EHGQE667-Cl7NltMv.js} +1 -1
  143. package/dist/assets/x-odTHAtLZ.js +1 -0
  144. package/dist/assets/{xychartDiagram-FW5EYKEG-DY5yRTUc.js → xychartDiagram-FW5EYKEG-DiMIxQ7N.js} +1 -1
  145. package/dist/index.html +21 -22
  146. package/package.json +8 -8
  147. package/src/features/apps/components/__tests__/app-packages-panel.test.tsx +234 -0
  148. package/src/features/apps/components/app-marketplace-dialog.tsx +262 -0
  149. package/src/features/apps/components/app-package-card.tsx +268 -0
  150. package/src/features/apps/components/app-packages-panel.tsx +301 -0
  151. package/src/features/apps/components/apps-panel.tsx +11 -4
  152. package/src/features/apps/hooks/use-app-marketplace.ts +95 -0
  153. package/src/features/apps/hooks/use-app-packages.ts +59 -0
  154. package/src/features/chat/components/chat-sidebar-list-mode-switch.tsx +27 -4
  155. package/src/features/chat/components/layout/__tests__/chat-sidebar-session-area.test.tsx +36 -5
  156. package/src/features/chat/components/layout/chat-sidebar-desktop-layout.tsx +12 -13
  157. package/src/features/chat/features/conversation/components/__tests__/session-conversation-area.test.tsx +79 -2
  158. package/src/features/chat/features/conversation/components/__tests__/session-conversation-input.streaming.test.tsx +4 -4
  159. package/src/features/chat/features/conversation/components/session-conversation-area.tsx +26 -5
  160. package/src/features/chat/features/conversation/components/session-conversation-input.tsx +3 -0
  161. package/src/features/chat/features/conversation/hooks/use-system-object-reference-select.ts +27 -0
  162. package/src/features/chat/features/conversation/utils/session-message-composer.utils.ts +5 -0
  163. package/src/features/chat/features/input/hooks/__tests__/use-chat-input-surface-state.test.tsx +102 -0
  164. package/src/features/chat/features/input/hooks/use-chat-input-surface-state.ts +71 -120
  165. package/src/features/chat/features/input/input-surface-plugins/__tests__/context-reference-plugin.test.ts +265 -18
  166. package/src/features/chat/features/input/input-surface-plugins/chat-input-product-plugin-adapters.types.ts +7 -2
  167. package/src/features/chat/features/input/input-surface-plugins/chat-input-product-plugin-adapters.utils.ts +124 -0
  168. package/src/features/chat/features/input/input-surface-plugins/context-reference-items.utils.ts +99 -15
  169. package/src/features/chat/features/input/input-surface-plugins/context-reference-plugin.utils.ts +143 -139
  170. package/src/features/chat/features/input/input-surface-plugins/panel-app-input-surface-items.utils.ts +116 -0
  171. package/src/features/chat/features/input/input-surface-plugins/slash-command-plugin.utils.ts +1 -1
  172. package/src/features/chat/features/input/utils/__tests__/chat-inline-token.utils.test.ts +53 -1
  173. package/src/features/chat/features/input/utils/chat-composer-token-protocol.utils.ts +5 -0
  174. package/src/features/chat/features/input/utils/chat-inline-token-composer.utils.ts +134 -0
  175. package/src/features/chat/features/input/utils/chat-inline-token.utils.ts +30 -101
  176. package/src/features/chat/features/input/utils/chat-system-object-reference.utils.test.ts +44 -0
  177. package/src/features/chat/features/input/utils/chat-system-object-reference.utils.ts +46 -0
  178. package/src/features/chat/features/ncp/hooks/__tests__/use-hydrated-ncp-agent.test.tsx +40 -3
  179. package/src/features/chat/features/workspace/components/__tests__/chat-session-workspace-file-preview.selection.test.tsx +66 -0
  180. package/src/features/chat/features/workspace/components/__tests__/chat-session-workspace-file-preview.test.tsx +13 -1
  181. package/src/features/chat/features/workspace/components/__tests__/chat-session-workspace-panel-content.test.tsx +653 -90
  182. package/src/features/chat/features/workspace/components/__tests__/chat-session-workspace-text-selection.test.tsx +12 -8
  183. package/src/features/chat/features/workspace/components/chat-session-workspace-directory-browser.tsx +401 -288
  184. package/src/features/chat/features/workspace/components/chat-session-workspace-file-breadcrumbs.tsx +54 -35
  185. package/src/features/chat/features/workspace/components/chat-session-workspace-file-preview.tsx +79 -148
  186. package/src/features/chat/features/workspace/components/chat-session-workspace-panel-content.tsx +156 -94
  187. package/src/features/chat/features/workspace/components/project-files/__tests__/workspace-directory-entry-menu.test.tsx +50 -0
  188. package/src/features/chat/features/workspace/components/project-files/workspace-directory-entry-menu.tsx +209 -0
  189. package/src/features/chat/features/workspace/components/project-files/workspace-directory-tree.tsx +459 -0
  190. package/src/features/chat/features/workspace/components/project-files/workspace-new-folder-tree-item.tsx +112 -0
  191. package/src/features/chat/features/workspace/components/project-files/workspace-project-files-toolbar.tsx +64 -0
  192. package/src/features/chat/features/workspace/hooks/use-workspace-explorer-layout.ts +85 -0
  193. package/src/features/chat/features/workspace/hooks/use-workspace-file-actions.ts +357 -0
  194. package/src/features/chat/features/workspace/utils/chat-workspace-file-path-mutation.utils.ts +58 -0
  195. package/src/features/chat/features/workspace/utils/chat-workspace-file-tab.utils.ts +23 -22
  196. package/src/features/chat/features/workspace/utils/chat-workspace-panel-layout.utils.ts +13 -4
  197. package/src/features/chat/index.ts +1 -1
  198. package/src/features/chat/managers/__tests__/chat-composer-intent.manager.test.ts +86 -60
  199. package/src/features/chat/managers/__tests__/chat-thread.manager.test.ts +103 -99
  200. package/src/features/chat/managers/chat-composer-intent.manager.ts +37 -33
  201. package/src/features/chat/managers/chat-draft-intent.manager.ts +18 -2
  202. package/src/features/chat/managers/chat-thread.manager.ts +132 -128
  203. package/src/features/chat/stores/__tests__/chat-thread.store.test.ts +26 -40
  204. package/src/features/chat/stores/chat-thread.store.ts +53 -74
  205. package/src/features/inbox/components/inbox-reader-dialog.test.tsx +38 -5
  206. package/src/features/inbox/components/inbox-reader-dialog.tsx +5 -3
  207. package/src/features/inbox/managers/inbox.manager.test.ts +28 -3
  208. package/src/features/inbox/managers/inbox.manager.ts +14 -4
  209. package/src/features/inbox/pages/inbox-page.test.ts +121 -2
  210. package/src/features/inbox/pages/inbox-page.tsx +5 -3
  211. package/src/features/marketplace/components/__tests__/marketplace-page.test.tsx +85 -1
  212. package/src/features/marketplace/hooks/__tests__/use-marketplace.test.tsx +44 -1
  213. package/src/features/marketplace/hooks/use-marketplace-item-actions.ts +23 -2
  214. package/src/features/marketplace/hooks/use-marketplace.ts +10 -0
  215. package/src/features/marketplace/index.ts +4 -0
  216. package/src/features/panel-apps/index.ts +5 -1
  217. package/src/features/panel-apps/utils/__tests__/panel-app-doc-browser.utils.test.ts +13 -6
  218. package/src/features/panel-apps/utils/panel-app-doc-browser.utils.tsx +6 -3
  219. package/src/features/right-panel-resources/index.ts +1 -0
  220. package/src/features/right-panel-resources/utils/right-panel-resource-uri.utils.ts +7 -4
  221. package/src/features/system-status/components/__tests__/desktop-update-config.test.tsx +20 -1
  222. package/src/features/system-status/components/desktop-update-config.tsx +14 -13
  223. package/src/features/system-status/managers/__tests__/runtime-update.manager.test.ts +99 -0
  224. package/src/features/system-status/managers/runtime-update.manager.ts +22 -0
  225. package/src/features/system-status/stores/runtime-update.store.ts +1 -0
  226. package/src/shared/components/resizable-right-panel/__tests__/resizable-right-panel.test.tsx +35 -38
  227. package/src/shared/components/resizable-right-panel/resizable-right-panel.tsx +25 -19
  228. package/src/shared/components/ui/actions/__tests__/icon-action-button.test.tsx +15 -0
  229. package/src/shared/components/ui/actions/icon-action-button.tsx +4 -2
  230. package/src/shared/components/ui/context-menu/__tests__/context-menu.test.tsx +60 -34
  231. package/src/shared/components/ui/context-menu/context-menu.tsx +95 -68
  232. package/src/shared/hooks/use-system-object-references.ts +22 -0
  233. package/src/shared/lib/api/managers/client.manager.ts +31 -17
  234. package/src/shared/lib/api/server-path/server-path.types.ts +66 -0
  235. package/src/shared/lib/api/types.ts +155 -116
  236. package/src/shared/lib/api/utils/server-path.utils.ts +35 -11
  237. package/src/shared/lib/i18n/locales/en-US/chat.json +58 -9
  238. package/src/shared/lib/i18n/locales/en-US/desktop-update.json +7 -4
  239. package/src/shared/lib/i18n/locales/en-US/doc-browser.json +40 -0
  240. package/src/shared/lib/i18n/locales/en-US/marketplace.json +3 -0
  241. package/src/shared/lib/i18n/locales/zh-CN/chat.json +60 -11
  242. package/src/shared/lib/i18n/locales/zh-CN/desktop-update.json +7 -4
  243. package/src/shared/lib/i18n/locales/zh-CN/doc-browser.json +40 -0
  244. package/src/shared/lib/i18n/locales/zh-CN/marketplace.json +3 -0
  245. package/dist/assets/appearance-settings-page-DpVJjvkB.js +0 -1
  246. package/dist/assets/architecture-TIHT7OUA-Bjp9jf0h.js +0 -1
  247. package/dist/assets/channel-5T3pdR-s.js +0 -1
  248. package/dist/assets/chat-page-BuFDmG5_.js +0 -1
  249. package/dist/assets/chunk-2Q5K7J3B-DTZ3tGfE.js +0 -1
  250. package/dist/assets/chunk-VAUOI2AC-HLGIHQ6k.js +0 -1
  251. package/dist/assets/chunk-XXDRQBXY-Dl0m6Ni-.js +0 -1
  252. package/dist/assets/classDiagram-OUVF2IWQ-DcDnzGNX.js +0 -1
  253. package/dist/assets/classDiagram-v2-EOCWNBFH-BKXKMzKM.js +0 -1
  254. package/dist/assets/config-split-page-BVWVDbvG.js +0 -1
  255. package/dist/assets/confirm-dialog-mA2iVzxh.js +0 -5
  256. package/dist/assets/cynefin-VYW2F7L2-BWg7U2UD.js +0 -1
  257. package/dist/assets/desktop-update-config-DyQb1LXr.js +0 -1
  258. package/dist/assets/doc-browser-CtO_HtRy.js +0 -1
  259. package/dist/assets/doc-browser-context-CuGI6PQc.js +0 -1
  260. package/dist/assets/doc-browser-eMUagyBV.js +0 -1
  261. package/dist/assets/eventmodeling-45OFAUF4-SB-J4tpq.js +0 -1
  262. package/dist/assets/flowDiagram-23GEKE2U-BQ_u-eLM.js +0 -1
  263. package/dist/assets/form-actions-BOfIyG3O.js +0 -1
  264. package/dist/assets/gitGraph-TEB2WS4Q-DPxNwsFd.js +0 -1
  265. package/dist/assets/index-BjBdWHV1.js +0 -107
  266. package/dist/assets/index-DmpmjAGQ.css +0 -1
  267. package/dist/assets/info-DKCQHKI2-DNlNGH90.js +0 -1
  268. package/dist/assets/loader-circle-Df8wF2Pr.js +0 -1
  269. package/dist/assets/mcp-marketplace-page-DI49IKxp.js +0 -1
  270. package/dist/assets/mcp-marketplace-page-ofDqbOIF.js +0 -9
  271. package/dist/assets/model-config-page-D73Tu3jW.js +0 -1
  272. package/dist/assets/packet-7NZHBO7P-BpeiqkBZ.js +0 -1
  273. package/dist/assets/pie-RZYD4A2V-Cy1yxsPd.js +0 -1
  274. package/dist/assets/plus-DVU5RLCS.js +0 -1
  275. package/dist/assets/provider-scoped-model-input-BaJJJYMX.js +0 -1
  276. package/dist/assets/providers-config-page-BL6IRVBm.js +0 -1
  277. package/dist/assets/radar-I7S5WNFK-CEcPKCj8.js +0 -1
  278. package/dist/assets/railroad-3IZDKUUU-CW38eNDs.js +0 -1
  279. package/dist/assets/railroad-abnf-AHOZXSZD-ByrylcXf.js +0 -1
  280. package/dist/assets/railroad-ebnf-EBAXGLYW-CNB6GwP-.js +0 -1
  281. package/dist/assets/railroad-peg-LSFZ7HO6-x6hnqlBG.js +0 -1
  282. package/dist/assets/react-BC_orpAj.js +0 -8
  283. package/dist/assets/remote-T24O58lH.js +0 -1
  284. package/dist/assets/rotate-cw-BqhwSV5p.js +0 -1
  285. package/dist/assets/runtime-config-page-CXUx017t.js +0 -1
  286. package/dist/assets/search-config-page-BB6d5gT7.js +0 -1
  287. package/dist/assets/secrets-config-page-DYsDeT9K.js +0 -3
  288. package/dist/assets/security-config-CVPgqL7G.js +0 -1
  289. package/dist/assets/skeleton-C5tWata3.js +0 -1
  290. package/dist/assets/stateDiagram-v2-6OUMAXLB-ZC6dFORv.js +0 -1
  291. package/dist/assets/swimlanesDiagram-G3AALYLV-CRkh51Im.js +0 -8
  292. package/dist/assets/treeView-QDETBFTQ-Bw0DSCH7.js +0 -1
  293. package/dist/assets/treemap-6X3UGDF4-DFWBFcfh.js +0 -1
  294. package/dist/assets/use-confirm-dialog-D_3-QpZU.js +0 -1
  295. package/dist/assets/wardley-OPB4EBWU-CoWturk0.js +0 -1
  296. package/dist/assets/x-BEWSqeux.js +0 -1
  297. /package/dist/assets/{array-NADKd9aU.js → array-BQVzeGRt.js} +0 -0
  298. /package/dist/assets/{chunk-KEIR6QF5-BuyuzJJL.js → chunk-KEIR6QF5-f9psyCXK.js} +0 -0
  299. /package/dist/assets/{chunk-Y2CYZVJY-D_B2ZRNt.js → chunk-Y2CYZVJY-D1mAU_Po.js} +0 -0
  300. /package/dist/assets/{config-hints-BcfVUMij.js → config-hints-nV5HPOvq.js} +0 -0
  301. /package/dist/assets/{cytoscape.esm-BANTlgza.js → cytoscape.esm-0uDtdD0l.js} +0 -0
  302. /package/dist/assets/{defaultLocale-C4_L0aEs.js → defaultLocale-DSWXGgt0.js} +0 -0
  303. /package/dist/assets/{dist-DAPEIJbz.js → dist--ta5rX12.js} +0 -0
  304. /package/dist/assets/{graphlib-ChqXg0dI.js → graphlib-CQ6QV4yP.js} +0 -0
  305. /package/dist/assets/{host-capabilities-D9C4CBK5.js → host-capabilities-Tk038i04.js} +0 -0
  306. /package/dist/assets/{init-D8wZg9xq.js → init-Y6h9jGtZ.js} +0 -0
  307. /package/dist/assets/{jszip.min-CkbBfwAQ.js → jszip.min-8hyJpgpx.js} +0 -0
  308. /package/dist/assets/{katex-TZJFa0J_.js → katex-ARhY9PS1.js} +0 -0
  309. /package/dist/assets/{middleware-B3k-TSmZ.js → middleware-DXsbf9xI.js} +0 -0
  310. /package/dist/assets/{path-DodfxcX7.js → path-10BjdyLh.js} +0 -0
  311. /package/dist/assets/{rough.esm-CUaa1Rbe.js → rough.esm-DxgC_14r.js} +0 -0
  312. /package/dist/assets/{xlsx-CxByi7bR.js → xlsx-DrDuDNAa.js} +0 -0
@@ -1,83 +1,201 @@
1
- import { fireEvent, render, screen } from "@testing-library/react";
2
- import userEvent from "@testing-library/user-event";
3
- import { expect, it, vi } from "vitest";
4
- import { ChatSessionWorkspacePanelContent } from "@/features/chat/features/workspace/components/chat-session-workspace-panel-content";
5
- import type { ResolvedChildSessionTab } from "@/features/chat/features/ncp/hooks/use-ncp-child-session-tabs-view";
1
+ import { fireEvent, render, screen, waitFor, within } from '@testing-library/react';
2
+ import userEvent from '@testing-library/user-event';
3
+ import { beforeEach, expect, it, vi } from 'vitest';
4
+ import { ChatSessionWorkspacePanelContent } from '@/features/chat/features/workspace/components/chat-session-workspace-panel-content';
5
+ import type { ResolvedChildSessionTab } from '@/features/chat/features/ncp/hooks/use-ncp-child-session-tabs-view';
6
+ import { useChatThreadStore } from '@/features/chat/stores/chat-thread.store';
7
+ import { CHAT_WORKSPACE_EXPLORER_DEFAULT_WIDTH } from '@/features/chat/features/workspace/utils/chat-workspace-panel-layout.utils';
6
8
 
7
9
  const mocks = vi.hoisted(() => ({
10
+ browseEntries: [
11
+ {
12
+ name: 'src',
13
+ path: '/Users/peiwang/Projects/nextbot/src',
14
+ kind: 'directory' as const,
15
+ hidden: false,
16
+ },
17
+ {
18
+ name: 'package.json',
19
+ path: '/private/resolved-worktree/package.json',
20
+ kind: 'file' as const,
21
+ hidden: false,
22
+ },
23
+ ],
8
24
  materializeSideChatDraft: vi.fn(),
25
+ createServerPathDirectory: vi.fn(),
26
+ createServerPathFile: vi.fn(),
27
+ deleteServerPathEntry: vi.fn(),
28
+ renameServerPathEntry: vi.fn(),
29
+ uploadServerPathFiles: vi.fn(),
30
+ refetchBrowse: vi.fn(),
31
+ requestDirectoryReference: vi.fn(),
9
32
  requestFileReference: vi.fn(),
10
33
  openChildSessions: vi.fn(),
11
34
  openFilePreview: vi.fn(),
12
35
  openProjectFiles: vi.fn(),
13
36
  openSessionCronPanel: vi.fn(),
37
+ removeWorkspacePath: vi.fn(),
38
+ renameWorkspacePath: vi.fn(),
14
39
  selectChildSessionDetail: vi.fn(),
40
+ setWorkspaceExplorerWidth: vi.fn(),
41
+ scrollIntoView: vi.fn(),
15
42
  }));
16
43
 
17
- vi.mock("@/features/chat/components/providers/chat-presenter.provider", () => ({
44
+ function firePointerEvent(
45
+ target: Window | Document | Node | Element,
46
+ type: string,
47
+ point: { clientX: number; pointerId?: number },
48
+ ) {
49
+ const event = new Event(type, { bubbles: true });
50
+ Object.defineProperties(event, {
51
+ clientX: { value: point.clientX },
52
+ pointerId: { value: point.pointerId ?? 1 },
53
+ });
54
+ fireEvent(target, event);
55
+ }
56
+
57
+ vi.mock('@/features/chat/components/providers/chat-presenter.provider', () => ({
18
58
  usePresenter: () => ({
19
59
  chatThreadManager: {
20
60
  ...mocks,
21
61
  },
22
62
  chatComposerIntentManager: {
63
+ requestDirectoryReference: mocks.requestDirectoryReference,
23
64
  requestFileReference: mocks.requestFileReference,
24
65
  },
25
66
  }),
26
67
  }));
27
68
 
28
- vi.mock("@/features/chat/features/conversation/components/session-conversation-area", () => ({
69
+ vi.mock('@/shared/lib/api', async (importOriginal) => ({
70
+ ...(await importOriginal()),
71
+ createServerPathDirectory: mocks.createServerPathDirectory,
72
+ createServerPathFile: mocks.createServerPathFile,
73
+ deleteServerPathEntry: mocks.deleteServerPathEntry,
74
+ renameServerPathEntry: mocks.renameServerPathEntry,
75
+ uploadServerPathFiles: mocks.uploadServerPathFiles,
76
+ }));
77
+
78
+ vi.mock('@/features/chat/features/conversation/components/session-conversation-area', () => ({
29
79
  SessionConversationArea: () => <div data-testid="session-conversation-area" />,
30
80
  }));
31
81
 
32
- vi.mock("@/shared/hooks/use-server-path-browse", () => ({
33
- useServerPathBrowse: () => ({
82
+ vi.mock('@/shared/hooks/use-server-path-browse', () => ({
83
+ useServerPathBrowse: ({ path }: { path?: string | null }) => ({
34
84
  isLoading: false,
35
85
  error: null,
36
- data: {
37
- currentPath: "/Users/peiwang/Projects/nextbot",
38
- parentPath: "/Users/peiwang/Projects",
39
- homePath: "/Users/peiwang",
40
- breadcrumbs: [],
41
- entries: [
42
- {
43
- name: "src",
44
- path: "/Users/peiwang/Projects/nextbot/src",
45
- kind: "directory",
46
- hidden: false,
86
+ data: path?.endsWith('package.json')
87
+ ? undefined
88
+ : {
89
+ currentPath: '/Users/peiwang/Projects/nextbot',
90
+ parentPath: '/Users/peiwang/Projects',
91
+ homePath: '/Users/peiwang',
92
+ breadcrumbs: [],
93
+ entries: mocks.browseEntries,
47
94
  },
48
- {
49
- name: "package.json",
50
- path: "/private/resolved-worktree/package.json",
51
- kind: "file",
52
- hidden: false,
53
- },
54
- ],
95
+ refetch: mocks.refetchBrowse,
96
+ }),
97
+ }));
98
+
99
+ vi.mock('@/shared/hooks/use-server-path-read', () => ({
100
+ useServerPathRead: ({ path }: { path: string }) => ({
101
+ isLoading: false,
102
+ error: null,
103
+ data: {
104
+ kind: 'text',
105
+ languageHint: 'json',
106
+ resolvedPath: path,
107
+ startLine: 1,
108
+ text: '{}',
109
+ truncated: false,
55
110
  },
56
111
  }),
57
112
  }));
58
113
 
114
+ beforeEach(() => {
115
+ vi.clearAllMocks();
116
+ HTMLElement.prototype.getBoundingClientRect = () => ({
117
+ bottom: 600,
118
+ height: 600,
119
+ left: 0,
120
+ right: 800,
121
+ top: 0,
122
+ width: 800,
123
+ x: 0,
124
+ y: 0,
125
+ toJSON: () => ({}),
126
+ });
127
+ mocks.browseEntries.splice(
128
+ 0,
129
+ mocks.browseEntries.length,
130
+ {
131
+ name: 'src',
132
+ path: '/Users/peiwang/Projects/nextbot/src',
133
+ kind: 'directory',
134
+ hidden: false,
135
+ },
136
+ {
137
+ name: 'package.json',
138
+ path: '/private/resolved-worktree/package.json',
139
+ kind: 'file',
140
+ hidden: false,
141
+ },
142
+ );
143
+ HTMLElement.prototype.scrollIntoView = mocks.scrollIntoView;
144
+ mocks.createServerPathDirectory.mockResolvedValue({
145
+ path: '/Users/peiwang/Projects/nextbot/new-folder',
146
+ });
147
+ mocks.createServerPathFile.mockResolvedValue({
148
+ path: '/Users/peiwang/Projects/nextbot/new-file.md',
149
+ });
150
+ mocks.renameServerPathEntry.mockResolvedValue({
151
+ path: '/Users/peiwang/Projects/nextbot/renamed.md',
152
+ oldPath: '/Users/peiwang/Projects/nextbot/package.json',
153
+ name: 'renamed.md',
154
+ kind: 'file',
155
+ });
156
+ mocks.deleteServerPathEntry.mockResolvedValue({
157
+ path: '/private/resolved-worktree/package.json',
158
+ kind: 'file',
159
+ });
160
+ mocks.uploadServerPathFiles.mockResolvedValue({
161
+ files: [
162
+ {
163
+ name: 'brief.md',
164
+ path: '/Users/peiwang/Projects/nextbot/brief.md',
165
+ sizeBytes: 5,
166
+ },
167
+ ],
168
+ overwritten: false,
169
+ });
170
+ mocks.refetchBrowse.mockResolvedValue(undefined);
171
+ useChatThreadStore.getState().setSnapshot({
172
+ workspaceExplorerOpen: false,
173
+ workspaceExplorerWidth: CHAT_WORKSPACE_EXPLORER_DEFAULT_WIDTH,
174
+ });
175
+ });
176
+
59
177
  function createChildTab(): ResolvedChildSessionTab {
60
178
  return {
61
- sessionKey: "child-1",
62
- parentSessionKey: "parent-1",
63
- title: "Child title",
64
- agentId: "agent-1",
179
+ sessionKey: 'child-1',
180
+ parentSessionKey: 'parent-1',
181
+ title: 'Child title',
182
+ agentId: 'agent-1',
65
183
  updatedAt: null,
66
184
  lastMessageAt: null,
67
185
  readAt: null,
68
186
  runStatus: undefined,
69
- sessionTypeLabel: "原生",
70
- preferredModel: "minimax/MiniMax-M3",
71
- projectName: "nextbot",
72
- projectRoot: "/Users/peiwang/Projects/nextbot",
187
+ sessionTypeLabel: '原生',
188
+ preferredModel: 'minimax/MiniMax-M3',
189
+ projectName: 'nextbot',
190
+ projectRoot: '/Users/peiwang/Projects/nextbot',
73
191
  };
74
192
  }
75
193
 
76
- it("shows compact child session metadata without repeating the tab title", () => {
194
+ it('shows compact child session metadata without repeating the tab title', () => {
77
195
  render(
78
196
  <ChatSessionWorkspacePanelContent
79
197
  activeSelection={{
80
- kind: "child-session",
198
+ kind: 'child-session',
81
199
  tab: createChildTab(),
82
200
  }}
83
201
  childSessionTabs={[createChildTab()]}
@@ -89,20 +207,20 @@ it("shows compact child session metadata without repeating the tab title", () =>
89
207
  />,
90
208
  );
91
209
 
92
- expect(screen.queryByText("Child title")).toBeNull();
93
- expect(screen.getByText("原生")).toBeTruthy();
94
- expect(screen.getByText("minimax/MiniMax-M3")).toBeTruthy();
95
- expect(screen.getByText("nextbot")).toBeTruthy();
96
- expect(screen.getByTitle("/Users/peiwang/Projects/nextbot")).toBeTruthy();
210
+ expect(screen.queryByText('Child title')).toBeNull();
211
+ expect(screen.getByText('原生')).toBeTruthy();
212
+ expect(screen.getByText('minimax/MiniMax-M3')).toBeTruthy();
213
+ expect(screen.getByText('nextbot')).toBeTruthy();
214
+ expect(screen.getByTitle('/Users/peiwang/Projects/nextbot')).toBeTruthy();
97
215
  });
98
216
 
99
- it("shows all session workspace entries in the overview", async () => {
217
+ it('shows all session workspace entries in the overview', async () => {
100
218
  const user = userEvent.setup();
101
219
  const childTab = createChildTab();
102
220
 
103
221
  render(
104
222
  <ChatSessionWorkspacePanelContent
105
- activeSelection={{ kind: "overview" }}
223
+ activeSelection={{ kind: 'overview' }}
106
224
  childSessionTabs={[childTab]}
107
225
  filePreviewRefreshVersion={0}
108
226
  sessionKey="parent-1"
@@ -112,11 +230,11 @@ it("shows all session workspace entries in the overview", async () => {
112
230
  />,
113
231
  );
114
232
 
115
- expect(screen.getByText("Overview")).toBeTruthy();
116
- const childSessionsButton = screen.getByRole("button", {
233
+ expect(screen.getByText('Overview')).toBeTruthy();
234
+ const childSessionsButton = screen.getByRole('button', {
117
235
  name: /Child sessions/,
118
236
  });
119
- const cronJobsButton = screen.getByRole("button", {
237
+ const cronJobsButton = screen.getByRole('button', {
120
238
  name: /Scheduled tasks/,
121
239
  });
122
240
 
@@ -125,17 +243,17 @@ it("shows all session workspace entries in the overview", async () => {
125
243
 
126
244
  await user.click(childSessionsButton);
127
245
  await user.click(cronJobsButton);
128
- await user.click(screen.getByRole("button", { name: /Project files/ }));
246
+ await user.click(screen.getByRole('button', { name: /Project files/ }));
129
247
 
130
- expect(mocks.openChildSessions).toHaveBeenCalledWith("parent-1");
131
- expect(mocks.openSessionCronPanel).toHaveBeenCalledWith("parent-1");
132
- expect(mocks.openProjectFiles).toHaveBeenCalledWith("parent-1");
248
+ expect(mocks.openChildSessions).toHaveBeenCalledWith('parent-1');
249
+ expect(mocks.openSessionCronPanel).toHaveBeenCalledWith('parent-1');
250
+ expect(mocks.openProjectFiles).toHaveBeenCalledWith('parent-1');
133
251
  });
134
252
 
135
- it("shows an empty child sessions page instead of disabling the entry", () => {
253
+ it('shows an empty child sessions page instead of disabling the entry', () => {
136
254
  render(
137
255
  <ChatSessionWorkspacePanelContent
138
- activeSelection={{ kind: "child-sessions" }}
256
+ activeSelection={{ kind: 'child-sessions' }}
139
257
  childSessionTabs={[]}
140
258
  filePreviewRefreshVersion={0}
141
259
  sessionKey="parent-1"
@@ -145,13 +263,13 @@ it("shows an empty child sessions page instead of disabling the entry", () => {
145
263
  />,
146
264
  );
147
265
 
148
- expect(screen.getByText("No child sessions yet.")).toBeTruthy();
266
+ expect(screen.getByText('No child sessions yet.')).toBeTruthy();
149
267
  });
150
268
 
151
- it("shows the selected session project as a hierarchical file tree", () => {
269
+ it('shows the selected session project as a hierarchical file tree', () => {
152
270
  render(
153
271
  <ChatSessionWorkspacePanelContent
154
- activeSelection={{ kind: "project-files" }}
272
+ activeSelection={{ kind: 'project-files' }}
155
273
  childSessionTabs={[]}
156
274
  filePreviewRefreshVersion={0}
157
275
  sessionKey="parent-1"
@@ -161,20 +279,18 @@ it("shows the selected session project as a hierarchical file tree", () => {
161
279
  />,
162
280
  );
163
281
 
164
- expect(screen.getByRole("tree", { name: "Project files" })).toBeTruthy();
165
- expect(screen.getByText("nextbot")).toBeTruthy();
166
- expect(
167
- screen.getByRole("treeitem", { name: "Open directory: src" }),
168
- ).toBeTruthy();
282
+ expect(screen.getByRole('tree', { name: 'Project files' })).toBeTruthy();
283
+ expect(screen.getByText('nextbot')).toBeTruthy();
284
+ expect(screen.getByRole('treeitem', { name: 'Open directory: src' })).toBeTruthy();
169
285
  expect(document.querySelector('[data-file-type-icon="npm"]')).toBeTruthy();
170
286
  });
171
287
 
172
- it("adds a project file to the targeted chat from its context menu", async () => {
288
+ it('adds a project file to the targeted chat from its context menu', async () => {
173
289
  const user = userEvent.setup();
174
290
  mocks.requestFileReference.mockReset();
175
291
  render(
176
292
  <ChatSessionWorkspacePanelContent
177
- activeSelection={{ kind: "project-files" }}
293
+ activeSelection={{ kind: 'project-files' }}
178
294
  childSessionTabs={[]}
179
295
  filePreviewRefreshVersion={0}
180
296
  sessionKey={null}
@@ -184,53 +300,500 @@ it("adds a project file to the targeted chat from its context menu", async () =>
184
300
  />,
185
301
  );
186
302
 
187
- fireEvent.contextMenu(
188
- screen.getByRole("button", { name: "package.json" }),
189
- );
303
+ fireEvent.contextMenu(screen.getByRole('button', { name: 'package.json' }));
190
304
 
191
- expect(screen.getByRole("menuitem", { name: "Copy path" })).toBeTruthy();
192
- expect(screen.getByRole("menuitem", { name: "Copy relative path" })).toBeTruthy();
193
- await user.click(screen.getByRole("menuitem", { name: "Add to chat" }));
305
+ expect(screen.getByRole('menuitem', { name: 'Copy path' })).toBeTruthy();
306
+ expect(screen.getByRole('menuitem', { name: 'Copy relative path' })).toBeTruthy();
307
+ await user.click(screen.getByRole('menuitem', { name: 'Add to chat' }));
194
308
 
195
309
  expect(mocks.requestFileReference).toHaveBeenCalledWith({
196
310
  targetSessionKey: null,
197
- tokenKey: "package.json",
198
- label: "package.json",
311
+ tokenKey: 'package.json',
312
+ label: 'package.json',
313
+ });
314
+ });
315
+
316
+ it('adds a project folder to chat with the directory reference protocol', async () => {
317
+ const user = userEvent.setup();
318
+ render(
319
+ <ChatSessionWorkspacePanelContent
320
+ activeSelection={{ kind: 'project-files' }}
321
+ childSessionTabs={[]}
322
+ filePreviewRefreshVersion={0}
323
+ sessionKey="parent-1"
324
+ sessionCronJobs={[]}
325
+ sessionProjectRoot="/Users/peiwang/Projects/nextbot"
326
+ sessionWorkingDir="/Users/peiwang/Projects/nextbot"
327
+ />,
328
+ );
329
+
330
+ fireEvent.contextMenu(screen.getByRole('button', { name: 'src' }));
331
+ await user.click(screen.getByRole('menuitem', { name: 'Add to chat' }));
332
+
333
+ expect(mocks.requestDirectoryReference).toHaveBeenCalledWith({
334
+ targetSessionKey: 'parent-1',
335
+ tokenKey: 'src',
336
+ label: 'src',
337
+ });
338
+ });
339
+
340
+ it('keeps the five Explorer actions distinct and exposes upload from the root menu', async () => {
341
+ const user = userEvent.setup();
342
+ render(
343
+ <ChatSessionWorkspacePanelContent
344
+ activeSelection={{ kind: 'project-files' }}
345
+ childSessionTabs={[]}
346
+ filePreviewRefreshVersion={0}
347
+ sessionKey="parent-1"
348
+ sessionCronJobs={[]}
349
+ sessionProjectRoot="/Users/peiwang/Projects/nextbot"
350
+ sessionWorkingDir="/Users/peiwang/Projects/nextbot"
351
+ />,
352
+ );
353
+
354
+ for (const name of ['New file', 'New folder', 'Refresh project files', 'Collapse all', 'More project file actions']) {
355
+ expect(screen.getByRole('button', { name })).toBeTruthy();
356
+ }
357
+ expect(screen.getByRole('button', { name: 'More actions for package.json' })).toBeTruthy();
358
+
359
+ await user.click(screen.getByRole('button', { name: 'More project file actions' }));
360
+ expect(screen.getByRole('menuitem', { name: 'Upload files here' })).toBeTruthy();
361
+ await user.keyboard('{Escape}');
362
+
363
+ fireEvent.contextMenu(screen.getByRole('button', { name: 'package.json' }));
364
+ const fileMenu = screen.getByRole('menu', {
365
+ name: 'Project file actions',
366
+ });
367
+ for (const name of ['Open', 'Add to chat', 'Download', 'Copy path', 'Copy relative path', 'Rename', 'Delete']) {
368
+ expect(within(fileMenu).getByRole('menuitem', { name })).toBeTruthy();
369
+ }
370
+ expect(within(fileMenu).getAllByRole('group')).toHaveLength(5);
371
+ const download = screen.getByRole('menuitem', { name: 'Download' });
372
+ expect(download.tagName).toBe('A');
373
+ expect(download.getAttribute('download')).toBe('package.json');
374
+ expect(download.getAttribute('href')).toContain(
375
+ '/api/server-paths/content?path=%2Fprivate%2Fresolved-worktree%2Fpackage.json',
376
+ );
377
+ });
378
+
379
+ it('creates a project file inline from the distinct toolbar action', async () => {
380
+ const user = userEvent.setup();
381
+ render(
382
+ <ChatSessionWorkspacePanelContent
383
+ activeSelection={{ kind: 'project-files' }}
384
+ childSessionTabs={[]}
385
+ filePreviewRefreshVersion={0}
386
+ sessionKey="parent-1"
387
+ sessionCronJobs={[]}
388
+ sessionProjectRoot="/Users/peiwang/Projects/nextbot"
389
+ sessionWorkingDir="/Users/peiwang/Projects/nextbot"
390
+ />,
391
+ );
392
+
393
+ await user.click(screen.getByRole('button', { name: 'New file' }));
394
+ const input = screen.getByRole('textbox', { name: 'File name' });
395
+ await user.type(input, 'notes.md');
396
+ await user.click(screen.getByRole('button', { name: 'Create' }));
397
+
398
+ await waitFor(() => {
399
+ expect(mocks.createServerPathFile).toHaveBeenCalledWith({
400
+ basePath: '/Users/peiwang/Projects/nextbot',
401
+ parentPath: '/Users/peiwang/Projects/nextbot',
402
+ name: 'notes.md',
403
+ });
404
+ });
405
+ });
406
+
407
+ it('renames a project file inline and synchronizes open workspace tabs', async () => {
408
+ const user = userEvent.setup();
409
+ render(
410
+ <ChatSessionWorkspacePanelContent
411
+ activeSelection={{ kind: 'project-files' }}
412
+ childSessionTabs={[]}
413
+ filePreviewRefreshVersion={0}
414
+ sessionKey="parent-1"
415
+ sessionCronJobs={[]}
416
+ sessionProjectRoot="/Users/peiwang/Projects/nextbot"
417
+ sessionWorkingDir="/Users/peiwang/Projects/nextbot"
418
+ />,
419
+ );
420
+
421
+ fireEvent.contextMenu(screen.getByRole('button', { name: 'package.json' }));
422
+ await user.click(screen.getByRole('menuitem', { name: 'Rename' }));
423
+ const input = screen.getByRole('textbox', { name: 'New name' });
424
+ await user.clear(input);
425
+ await user.type(input, 'renamed.md');
426
+ await user.click(screen.getByRole('button', { name: 'Rename' }));
427
+
428
+ await waitFor(() => {
429
+ expect(mocks.renameServerPathEntry).toHaveBeenCalledWith({
430
+ basePath: '/Users/peiwang/Projects/nextbot',
431
+ path: '/private/resolved-worktree/package.json',
432
+ name: 'renamed.md',
433
+ });
434
+ expect(mocks.renameWorkspacePath).toHaveBeenCalledWith({
435
+ previousPath: '/Users/peiwang/Projects/nextbot/package.json',
436
+ path: '/Users/peiwang/Projects/nextbot/renamed.md',
437
+ label: 'renamed.md',
438
+ });
199
439
  });
200
440
  });
201
441
 
202
- it("preserves project tree expansion and scroll while visiting another workspace tab", async () => {
442
+ it('creates a project folder from the root toolbar and refreshes the tree', async () => {
443
+ const user = userEvent.setup();
444
+ mocks.createServerPathDirectory.mockImplementationOnce(async () => {
445
+ const path = '/Users/peiwang/Projects/nextbot/research';
446
+ mocks.browseEntries.push({
447
+ name: 'research',
448
+ path,
449
+ kind: 'directory',
450
+ hidden: false,
451
+ });
452
+ return { path };
453
+ });
454
+ render(
455
+ <ChatSessionWorkspacePanelContent
456
+ activeSelection={{ kind: 'project-files' }}
457
+ childSessionTabs={[]}
458
+ filePreviewRefreshVersion={0}
459
+ sessionKey="parent-1"
460
+ sessionCronJobs={[]}
461
+ sessionProjectRoot="/Users/peiwang/Projects/nextbot"
462
+ sessionWorkingDir="/Users/peiwang/Projects/nextbot"
463
+ />,
464
+ );
465
+
466
+ await user.click(screen.getByRole('button', { name: 'New folder' }));
467
+ expect(document.activeElement).toBe(screen.getByRole('textbox', { name: 'Folder name' }));
468
+ expect(mocks.scrollIntoView).toHaveBeenCalledWith({ block: 'nearest' });
469
+ await user.type(screen.getByRole('textbox', { name: 'Folder name' }), 'research');
470
+ await user.click(screen.getByRole('button', { name: 'Create' }));
471
+
472
+ await waitFor(() => {
473
+ expect(mocks.createServerPathDirectory).toHaveBeenCalledWith({
474
+ basePath: '/Users/peiwang/Projects/nextbot',
475
+ parentPath: '/Users/peiwang/Projects/nextbot',
476
+ name: 'research',
477
+ });
478
+ expect(mocks.refetchBrowse).toHaveBeenCalled();
479
+ expect(document.activeElement).toBe(screen.getByRole('button', { name: 'research' }));
480
+ expect(mocks.scrollIntoView).toHaveBeenCalledTimes(2);
481
+ });
482
+ });
483
+
484
+ it('uses the selected directory as the toolbar target and creates inline', async () => {
485
+ const user = userEvent.setup();
486
+ render(
487
+ <ChatSessionWorkspacePanelContent
488
+ activeSelection={{ kind: 'project-files' }}
489
+ childSessionTabs={[]}
490
+ filePreviewRefreshVersion={0}
491
+ sessionKey="parent-1"
492
+ sessionCronJobs={[]}
493
+ sessionProjectRoot="/Users/peiwang/Projects/nextbot"
494
+ sessionWorkingDir="/Users/peiwang/Projects/nextbot"
495
+ />,
496
+ );
497
+
498
+ await user.click(screen.getByRole('button', { name: 'src' }));
499
+ await user.click(screen.getByRole('button', { name: 'New folder' }));
500
+
501
+ const input = screen.getByRole('textbox', { name: 'Folder name' });
502
+ expect(input.closest('[role="group"]')?.parentElement?.getAttribute('aria-label')).toBe('Open directory: src');
503
+ await user.type(input, 'nested');
504
+ await user.click(screen.getByRole('button', { name: 'Create' }));
505
+
506
+ await waitFor(() => {
507
+ expect(mocks.createServerPathDirectory).toHaveBeenCalledWith({
508
+ basePath: '/Users/peiwang/Projects/nextbot',
509
+ parentPath: '/Users/peiwang/Projects/nextbot/src',
510
+ name: 'nested',
511
+ });
512
+ });
513
+ });
514
+
515
+ it('opens root actions from blank tree space and reveals an inline draft', async () => {
516
+ const user = userEvent.setup();
517
+ render(
518
+ <ChatSessionWorkspacePanelContent
519
+ activeSelection={{ kind: 'project-files' }}
520
+ childSessionTabs={[]}
521
+ filePreviewRefreshVersion={0}
522
+ sessionKey="parent-1"
523
+ sessionCronJobs={[]}
524
+ sessionProjectRoot="/Users/peiwang/Projects/nextbot"
525
+ sessionWorkingDir="/Users/peiwang/Projects/nextbot"
526
+ />,
527
+ );
528
+
529
+ fireEvent.contextMenu(screen.getByRole('tree', { name: 'Project files' }));
530
+ expect(screen.getByRole('menu', { name: 'Project root actions' })).toBeTruthy();
531
+ expect(screen.getByRole('menuitem', { name: 'Upload files here' })).toBeTruthy();
532
+ await user.click(screen.getByRole('menuitem', { name: 'New folder' }));
533
+
534
+ const input = screen.getByRole('textbox', { name: 'Folder name' });
535
+ expect(document.activeElement).toBe(input);
536
+ expect(mocks.scrollIntoView).toHaveBeenCalledWith({ block: 'nearest' });
537
+ });
538
+
539
+ it('uploads multiple project files from the root actions menu', async () => {
540
+ const user = userEvent.setup();
541
+ render(
542
+ <ChatSessionWorkspacePanelContent
543
+ activeSelection={{ kind: 'project-files' }}
544
+ childSessionTabs={[]}
545
+ filePreviewRefreshVersion={0}
546
+ sessionKey="parent-1"
547
+ sessionCronJobs={[]}
548
+ sessionProjectRoot="/Users/peiwang/Projects/nextbot"
549
+ sessionWorkingDir="/Users/peiwang/Projects/nextbot"
550
+ />,
551
+ );
552
+
553
+ await user.click(screen.getByRole('button', { name: 'More project file actions' }));
554
+ await user.click(screen.getByRole('menuitem', { name: 'Upload files here' }));
555
+ const file = new File(['brief'], 'brief.md', { type: 'text/markdown' });
556
+ fireEvent.change(screen.getByLabelText('Upload files'), {
557
+ target: { files: [file] },
558
+ });
559
+
560
+ await waitFor(() => {
561
+ expect(mocks.uploadServerPathFiles).toHaveBeenCalledWith({
562
+ basePath: '/Users/peiwang/Projects/nextbot',
563
+ targetPath: '/Users/peiwang/Projects/nextbot',
564
+ files: [file],
565
+ overwrite: undefined,
566
+ });
567
+ expect(mocks.refetchBrowse).toHaveBeenCalled();
568
+ });
569
+ });
570
+
571
+ it('requires destructive confirmation before deleting a project file', async () => {
572
+ const user = userEvent.setup();
573
+ render(
574
+ <ChatSessionWorkspacePanelContent
575
+ activeSelection={{ kind: 'project-files' }}
576
+ childSessionTabs={[]}
577
+ filePreviewRefreshVersion={0}
578
+ sessionKey="parent-1"
579
+ sessionCronJobs={[]}
580
+ sessionProjectRoot="/Users/peiwang/Projects/nextbot"
581
+ sessionWorkingDir="/Users/peiwang/Projects/nextbot"
582
+ />,
583
+ );
584
+
585
+ fireEvent.contextMenu(screen.getByRole('button', { name: 'package.json' }));
586
+ await user.click(screen.getByRole('menuitem', { name: 'Delete' }));
587
+ expect(mocks.deleteServerPathEntry).not.toHaveBeenCalled();
588
+ expect(screen.getByText('Delete package.json?')).toBeTruthy();
589
+
590
+ await user.click(screen.getByRole('button', { name: 'Delete' }));
591
+ await waitFor(() => {
592
+ expect(mocks.deleteServerPathEntry).toHaveBeenCalledWith({
593
+ basePath: '/Users/peiwang/Projects/nextbot',
594
+ path: '/private/resolved-worktree/package.json',
595
+ });
596
+ expect(mocks.removeWorkspacePath).toHaveBeenCalledWith('/private/resolved-worktree/package.json');
597
+ expect(mocks.refetchBrowse).toHaveBeenCalled();
598
+ });
599
+ });
600
+
601
+ it('preserves project tree expansion and scroll while visiting another workspace tab', async () => {
203
602
  const user = userEvent.setup();
204
603
  const projectFilesProps = {
205
- activeSelection: { kind: "project-files" as const },
604
+ activeSelection: { kind: 'project-files' as const },
206
605
  childSessionTabs: [],
207
606
  filePreviewRefreshVersion: 0,
208
- sessionKey: "parent-1",
607
+ sessionKey: 'parent-1',
209
608
  sessionCronJobs: [],
210
- sessionProjectRoot: "/Users/peiwang/Projects/nextbot",
211
- sessionWorkingDir: "/Users/peiwang/Projects/nextbot",
609
+ sessionProjectRoot: '/Users/peiwang/Projects/nextbot',
610
+ sessionWorkingDir: '/Users/peiwang/Projects/nextbot',
212
611
  };
213
- const { rerender } = render(
214
- <ChatSessionWorkspacePanelContent {...projectFilesProps} />,
215
- );
216
- const tree = screen.getByRole("tree", { name: "Project files" });
217
- const sourceDirectory = screen.getByRole("treeitem", {
218
- name: "Open directory: src",
612
+ const { rerender } = render(<ChatSessionWorkspacePanelContent {...projectFilesProps} />);
613
+ const tree = screen.getByRole('tree', { name: 'Project files' });
614
+ const sourceDirectory = screen.getByRole('treeitem', {
615
+ name: 'Open directory: src',
219
616
  });
220
- await user.click(screen.getByRole("button", { name: "src" }));
617
+ await user.click(screen.getByRole('button', { name: 'src' }));
221
618
  tree.scrollTop = 128;
222
- expect(sourceDirectory.getAttribute("aria-expanded")).toBe("true");
619
+ expect(sourceDirectory.getAttribute('aria-expanded')).toBe('true');
620
+
621
+ rerender(<ChatSessionWorkspacePanelContent {...projectFilesProps} activeSelection={{ kind: 'overview' }} />);
622
+ rerender(<ChatSessionWorkspacePanelContent {...projectFilesProps} />);
623
+
624
+ expect(screen.getByRole('tree', { name: 'Project files' })).toBe(tree);
625
+ expect(sourceDirectory.isConnected).toBe(true);
626
+ expect(sourceDirectory.getAttribute('aria-expanded')).toBe('true');
627
+ expect(tree.scrollTop).toBe(128);
628
+ });
629
+
630
+ it('selects and reveals the active file when it is opened outside the Explorer', async () => {
631
+ const user = userEvent.setup();
632
+ const projectFilesProps = {
633
+ activeSelection: { kind: 'project-files' as const },
634
+ childSessionTabs: [],
635
+ filePreviewRefreshVersion: 0,
636
+ sessionKey: 'parent-1',
637
+ sessionCronJobs: [],
638
+ sessionProjectRoot: '/Users/peiwang/Projects/nextbot',
639
+ sessionWorkingDir: '/Users/peiwang/Projects/nextbot',
640
+ };
641
+ const { rerender } = render(<ChatSessionWorkspacePanelContent {...projectFilesProps} />);
642
+ const sourceDirectory = screen.getByRole('treeitem', { name: 'Open directory: src' });
643
+ const packageEntry = screen.getByRole('treeitem', { name: 'Open file: package.json' });
644
+ await user.click(screen.getByRole('button', { name: 'src' }));
645
+ expect(sourceDirectory.getAttribute('aria-selected')).toBe('true');
223
646
 
224
647
  rerender(
225
648
  <ChatSessionWorkspacePanelContent
226
649
  {...projectFilesProps}
227
- activeSelection={{ kind: "overview" }}
650
+ activeSelection={{
651
+ kind: 'file',
652
+ file: {
653
+ key: 'parent-1::preview::package.json',
654
+ parentSessionKey: 'parent-1',
655
+ path: 'package.json',
656
+ label: 'package.json',
657
+ viewMode: 'preview',
658
+ previewViewer: 'source',
659
+ },
660
+ }}
228
661
  />,
229
662
  );
230
- rerender(<ChatSessionWorkspacePanelContent {...projectFilesProps} />);
231
663
 
232
- expect(screen.getByRole("tree", { name: "Project files" })).toBe(tree);
233
- expect(sourceDirectory.isConnected).toBe(true);
234
- expect(sourceDirectory.getAttribute("aria-expanded")).toBe("true");
235
- expect(tree.scrollTop).toBe(128);
664
+ await waitFor(() => {
665
+ expect(packageEntry.getAttribute('aria-selected')).toBe('true');
666
+ expect(sourceDirectory.getAttribute('aria-selected')).toBe('false');
667
+ });
668
+ expect(mocks.scrollIntoView).toHaveBeenCalled();
669
+ expect(document.activeElement).not.toBe(screen.getByRole('button', { name: 'package.json' }));
670
+ });
671
+
672
+ it('expands active-file ancestors like an auto-revealing Explorer', async () => {
673
+ useChatThreadStore.getState().setSnapshot({ workspaceExplorerOpen: true });
674
+ render(
675
+ <ChatSessionWorkspacePanelContent
676
+ activeSelection={{
677
+ kind: 'file',
678
+ file: {
679
+ key: 'parent-1::preview::src/index.ts',
680
+ parentSessionKey: 'parent-1',
681
+ path: '/Users/peiwang/Projects/nextbot/src/index.ts',
682
+ label: 'index.ts',
683
+ viewMode: 'preview',
684
+ previewViewer: 'source',
685
+ },
686
+ }}
687
+ childSessionTabs={[]}
688
+ filePreviewRefreshVersion={0}
689
+ sessionKey="parent-1"
690
+ sessionCronJobs={[]}
691
+ sessionProjectRoot="/Users/peiwang/Projects/nextbot"
692
+ sessionWorkingDir="/Users/peiwang/Projects/nextbot"
693
+ />,
694
+ );
695
+
696
+ const sourceDirectory = within(screen.getByTestId('workspace-shared-explorer')).getAllByRole('treeitem', {
697
+ name: 'Open directory: src',
698
+ })[0];
699
+ await waitFor(() => {
700
+ expect(sourceDirectory.getAttribute('aria-expanded')).toBe('true');
701
+ });
702
+ });
703
+
704
+ it('keeps the file Explorer closed by default and reuses it after an explicit open', () => {
705
+ const projectFilesProps = {
706
+ activeSelection: { kind: 'project-files' as const },
707
+ childSessionTabs: [],
708
+ filePreviewRefreshVersion: 0,
709
+ sessionKey: 'parent-1',
710
+ sessionCronJobs: [],
711
+ sessionProjectRoot: '/Users/peiwang/Projects/nextbot',
712
+ sessionWorkingDir: '/Users/peiwang/Projects/nextbot',
713
+ };
714
+ const { rerender } = render(<ChatSessionWorkspacePanelContent {...projectFilesProps} />);
715
+ const tree = screen.getByRole('tree', { name: 'Project files' });
716
+
717
+ rerender(
718
+ <ChatSessionWorkspacePanelContent
719
+ {...projectFilesProps}
720
+ activeSelection={{
721
+ kind: 'file',
722
+ file: {
723
+ key: 'parent-1::preview::package.json',
724
+ parentSessionKey: 'parent-1',
725
+ path: '/Users/peiwang/Projects/nextbot/package.json',
726
+ label: 'package.json',
727
+ viewMode: 'preview',
728
+ previewViewer: 'source',
729
+ },
730
+ }}
731
+ />,
732
+ );
733
+
734
+ const explorer = screen.getByTestId('workspace-shared-explorer');
735
+ const breadcrumbs = screen.getByTestId('workspace-file-breadcrumbs');
736
+ const toggle = screen.getByTestId('workspace-explorer-toggle');
737
+ expect(explorer.getAttribute('aria-hidden')).toBe('true');
738
+ fireEvent.click(toggle);
739
+ expect(useChatThreadStore.getState().snapshot.workspaceExplorerOpen).toBe(true);
740
+ expect(screen.getByRole('tree', { name: 'Project files' })).toBe(tree);
741
+ expect(explorer.getAttribute('data-mode')).toBe('side');
742
+ expect(explorer.style.width).toBe('224px');
743
+ expect(breadcrumbs.contains(toggle)).toBe(true);
744
+ expect(toggle.parentElement?.className).not.toContain('border-r');
745
+
746
+ const handle = screen.getByRole('separator', {
747
+ name: 'Resize project files',
748
+ });
749
+ firePointerEvent(handle, 'pointerdown', { clientX: 224, pointerId: 1 });
750
+ firePointerEvent(window, 'pointermove', { clientX: 300, pointerId: 1 });
751
+ firePointerEvent(window, 'pointerup', { clientX: 300, pointerId: 1 });
752
+ expect(mocks.setWorkspaceExplorerWidth).toHaveBeenCalledWith(300);
753
+ });
754
+
755
+ it('uses an overlay only below the compact threshold and persists closing it without unmounting', () => {
756
+ HTMLElement.prototype.getBoundingClientRect = () => ({
757
+ bottom: 600,
758
+ height: 600,
759
+ left: 0,
760
+ right: 540,
761
+ top: 0,
762
+ width: 540,
763
+ x: 0,
764
+ y: 0,
765
+ toJSON: () => ({}),
766
+ });
767
+ useChatThreadStore.getState().setSnapshot({ workspaceExplorerOpen: true });
768
+ render(
769
+ <ChatSessionWorkspacePanelContent
770
+ activeSelection={{
771
+ kind: 'file',
772
+ file: {
773
+ key: 'parent-1::preview::package.json',
774
+ parentSessionKey: 'parent-1',
775
+ path: '/Users/peiwang/Projects/nextbot/package.json',
776
+ label: 'package.json',
777
+ viewMode: 'preview',
778
+ previewViewer: 'source',
779
+ },
780
+ }}
781
+ childSessionTabs={[]}
782
+ filePreviewRefreshVersion={0}
783
+ sessionKey="parent-1"
784
+ sessionCronJobs={[]}
785
+ sessionProjectRoot="/Users/peiwang/Projects/nextbot"
786
+ sessionWorkingDir="/Users/peiwang/Projects/nextbot"
787
+ />,
788
+ );
789
+
790
+ const explorer = screen.getByTestId('workspace-shared-explorer');
791
+ const tree = screen.getByRole('tree', { name: 'Project files' });
792
+ expect(explorer.getAttribute('data-mode')).toBe('overlay');
793
+ expect(screen.queryByTestId('workspace-explorer-resize-handle')).toBeNull();
794
+
795
+ fireEvent.click(screen.getByTestId('workspace-explorer-scrim'));
796
+ expect(useChatThreadStore.getState().snapshot.workspaceExplorerOpen).toBe(false);
797
+ expect(explorer.getAttribute('aria-hidden')).toBe('true');
798
+ expect(tree.isConnected).toBe(true);
236
799
  });