@nextclaw/ui 0.15.3 → 0.15.5

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 (314) hide show
  1. package/CHANGELOG.md +38 -0
  2. package/dist/assets/abnfDiagram-VRR7QNED-BYpK0F5F.js +1 -0
  3. package/dist/assets/aiden0z-pptx-renderer.es-D87ywFdC.js +94 -0
  4. package/dist/assets/api-IlX_CbwU.js +13 -0
  5. package/dist/assets/app-presenter-provider-Bbl_MVbg.js +106 -0
  6. package/dist/assets/app-presenter-provider-D3BuuOyZ.css +1 -0
  7. package/dist/assets/appearance-settings-page-80fLa4-9.js +1 -0
  8. package/dist/assets/arc-DFELHWM2.js +1 -0
  9. package/dist/assets/architecture-TIHT7OUA-ClwVFq-K.js +1 -0
  10. package/dist/assets/architectureDiagram-ZJ3FMSHR-CvRhdovL.js +36 -0
  11. package/dist/assets/array-BqYdCIE6.js +1 -0
  12. package/dist/assets/blockDiagram-677ZJIJ3-DVYNkbR_.js +132 -0
  13. package/dist/assets/{book-open-C3mJGwiy.js → book-open-CN7Shpij.js} +1 -1
  14. package/dist/assets/c4Diagram-LMCZKHZV-B1w-iwRe.js +10 -0
  15. package/dist/assets/channel-BEHOSFc5.js +1 -0
  16. package/dist/assets/channels-list-page-R7M6pzp6.js +8 -0
  17. package/dist/assets/chat-page-152YuwfU.js +1 -0
  18. package/dist/assets/chunk-2Q5K7J3B-DrIhesSX.js +1 -0
  19. package/dist/assets/chunk-32BRIVSS-kG3lg0bC.js +1 -0
  20. package/dist/assets/chunk-52WLFC77-BLeB7iGk.js +10 -0
  21. package/dist/assets/chunk-5FCAYU7R-BQga1Rr7.js +1 -0
  22. package/dist/assets/chunk-5HE753X5-DU81hkcF.js +1 -0
  23. package/dist/assets/chunk-5JV3BV7I-C2bAx0m7.js +1 -0
  24. package/dist/assets/chunk-5TONJI2A-BExIIgWY.js +2 -0
  25. package/dist/assets/chunk-5VM5RSS4-C3XB05yh.js +15 -0
  26. package/dist/assets/chunk-7BUUIJ7U-JiyRtrGu.js +1 -0
  27. package/dist/assets/chunk-BIQX33UG-BZRV20pI.js +1 -0
  28. package/dist/assets/chunk-C7G6YPKG-AsyDSQ9j.js +1 -0
  29. package/dist/assets/chunk-CQNSW5MT-iN-fiXVA.js +1 -0
  30. package/dist/assets/chunk-CYSBUYHQ-DhC_TRzI.js +1 -0
  31. package/dist/assets/chunk-DseTPa7n.js +1 -0
  32. package/dist/assets/chunk-EMLP6XTP-QFLK8R6d.js +1 -0
  33. package/dist/assets/chunk-EX3LRPZG-DzUwhkCz.js +231 -0
  34. package/dist/assets/chunk-FWX5IMBZ-DFelpZBy.js +2 -0
  35. package/dist/assets/chunk-HOUHSVGY-BMPwFEwN.js +1 -0
  36. package/dist/assets/chunk-ICXQ74PX-DH8NJMok.js +2 -0
  37. package/dist/assets/chunk-JG7HCLWE-D-95AsO4.js +2 -0
  38. package/dist/assets/chunk-JWPE2WC7-DIaTv8kx.js +1 -0
  39. package/dist/assets/chunk-KEIR6QF5-A3jEqwzn.js +161 -0
  40. package/dist/assets/chunk-MOJQB5TN-BPFLzWgj.js +88 -0
  41. package/dist/assets/chunk-MOZMSUNE-DR_fzxG0.js +1 -0
  42. package/dist/assets/chunk-OGEWGWER-TIcEoPPH.js +1 -0
  43. package/dist/assets/chunk-OSBZ3O6U-BALNvSfG.js +1 -0
  44. package/dist/assets/chunk-PUDLZKDR-I-EXhf_k.js +156 -0
  45. package/dist/assets/chunk-Q4XR5HBZ-uvIGstBS.js +70 -0
  46. package/dist/assets/chunk-QBLGF6JB-r-TjLdxa.js +1 -0
  47. package/dist/assets/chunk-R7FJI6CG-N-1LDgvy.js +1 -0
  48. package/dist/assets/chunk-RYQCIY6F-jxNASx65.js +1 -0
  49. package/dist/assets/chunk-U6XO7XAA-Cphbuc7q.js +2 -0
  50. package/dist/assets/chunk-V7JOEXUC-BLEuBf1s.js +206 -0
  51. package/dist/assets/chunk-VAUOI2AC-Dpcz6BhT.js +1 -0
  52. package/dist/assets/chunk-VR4S4FIN-BAUbR0ke.js +1 -0
  53. package/dist/assets/chunk-WYO6CB5R-Do6SJrnY.js +127 -0
  54. package/dist/assets/chunk-XXDRQBXY-C2QjjwYX.js +1 -0
  55. package/dist/assets/chunk-Y2CYZVJY-BGrd8tLy.js +1 -0
  56. package/dist/assets/chunk-YOTPTUD7-CQqvMTCr.js +1 -0
  57. package/dist/assets/chunk-ZGVPDNZ5-B7kWWaXv.js +62 -0
  58. package/dist/assets/chunk-ZIRB5QZD-DTU0cDNh.js +32 -0
  59. package/dist/assets/classDiagram-OUVF2IWQ-DESMaY3_.js +1 -0
  60. package/dist/assets/classDiagram-v2-EOCWNBFH-DwOlByYH.js +1 -0
  61. package/dist/assets/config-split-page-BlfvfmTm.js +1 -0
  62. package/dist/assets/confirm-dialog-Dt8Kdbgp.js +5 -0
  63. package/dist/assets/cose-bilkent-JH36ORCC-B9LrtV-q.js +1 -0
  64. package/dist/assets/cynefin-VYW2F7L2-CzjeeOq9.js +1 -0
  65. package/dist/assets/cynefinDiagram-TSTJHNR4-BfjvW4su.js +62 -0
  66. package/dist/assets/cytoscape.esm-CRW8q92n.js +321 -0
  67. package/dist/assets/dagre-DkiXZKkL.js +1 -0
  68. package/dist/assets/dagre-VKFMJZFB-B1pQKQSY.js +4 -0
  69. package/dist/assets/defaultLocale-Cdv9x6q7.js +1 -0
  70. package/dist/assets/desktop-update-config-DreMJu9z.js +1 -0
  71. package/dist/assets/diagram-FQU43EPY-cB4EZdaP.js +3 -0
  72. package/dist/assets/diagram-G47NLZAW-CtW8im1B.js +24 -0
  73. package/dist/assets/diagram-NH7WQ7WH-BDCtrCCm.js +24 -0
  74. package/dist/assets/diagram-OA4YK3LP-DEMJ42Gt.js +30 -0
  75. package/dist/assets/diagram-WEI45ONY-CdCcxSYU.js +41 -0
  76. package/dist/assets/dist-D5ycbwCo.js +41 -0
  77. package/dist/assets/dist-dw-gAfM_.js +1 -0
  78. package/dist/assets/doc-browser-C9u7N9vH.js +1 -0
  79. package/dist/assets/doc-browser-DJQ5XPsi2.js +1 -0
  80. package/dist/assets/doc-browser-context-CCdW9TJf.js +1 -0
  81. package/dist/assets/docx-preview-zis3U0Fa.js +20 -0
  82. package/dist/assets/download-CyPgglKr.js +1 -0
  83. package/dist/assets/ebnfDiagram-CCIWWBDH-DBZETBwE.js +1 -0
  84. package/dist/assets/erDiagram-Q63AITRT-y2zDXprP.js +85 -0
  85. package/dist/assets/eventmodeling-45OFAUF4-C38BqG8w.js +1 -0
  86. package/dist/assets/{external-link-CU0kzzIP.js → external-link-BahW1sl3.js} +1 -1
  87. package/dist/assets/eye-D8gGXMkD.js +1 -0
  88. package/dist/assets/flowDiagram-23GEKE2U-BoKk8RRq.js +1 -0
  89. package/dist/assets/folder-DGWqFDvE.js +1 -0
  90. package/dist/assets/{form-actions-BvCp_0lS.js → form-actions-D2imZLnU.js} +1 -1
  91. package/dist/assets/ganttDiagram-NO4QXBWP-D2DP344e.js +292 -0
  92. package/dist/assets/gitGraph-TEB2WS4Q-IsQHHJuS.js +1 -0
  93. package/dist/assets/gitGraphDiagram-IHSO6WYX-D37uiwA2.js +106 -0
  94. package/dist/assets/graphlib-BcNFQr5y.js +1 -0
  95. package/dist/assets/index-0av6WO9-.css +1 -0
  96. package/dist/assets/index-CT0iYlxV.js +2 -0
  97. package/dist/assets/info-DKCQHKI2-ofy2qlHI.js +1 -0
  98. package/dist/assets/infoDiagram-FWYZ7A6U-CG-WAIRi.js +2 -0
  99. package/dist/assets/init-D8TjIqfm.js +1 -0
  100. package/dist/assets/ishikawaDiagram-FXEZZL3T-WeCTnW0f.js +70 -0
  101. package/dist/assets/journeyDiagram-5HDEW3XC-DUFBW9Td.js +139 -0
  102. package/dist/assets/jszip.min-CP5uPNQa.js +2 -0
  103. package/dist/assets/kanban-definition-HUTT4EX6-B0lxgMpg.js +89 -0
  104. package/dist/assets/katex-wEmrEFTg.js +257 -0
  105. package/dist/assets/line-B78frP_c.js +1 -0
  106. package/dist/assets/linear-C6lVEcb2.js +1 -0
  107. package/dist/assets/loader-circle-DtKek-qG.js +1 -0
  108. package/dist/assets/logo-badge-CuPetu-v.js +1 -0
  109. package/dist/assets/map-Bdri5UL5.js +1 -0
  110. package/dist/assets/mcp-marketplace-page-4qMwFDpz.js +1 -0
  111. package/dist/assets/mcp-marketplace-page-BbxN5aZF.js +9 -0
  112. package/dist/assets/mermaid-parser.core-Cue18vLo.js +4 -0
  113. package/dist/assets/mermaid.core-CXwWRh5i.js +11 -0
  114. package/dist/assets/mindmap-definition-LN4V7U3C-CFxz0P0-.js +96 -0
  115. package/dist/assets/model-config-page-DiJcvBML.js +1 -0
  116. package/dist/assets/ordinal-Cbd8t4LB.js +1 -0
  117. package/dist/assets/packet-7NZHBO7P-Rfopfa1o.js +1 -0
  118. package/dist/assets/path-DzBnMI1r.js +1 -0
  119. package/dist/assets/pegDiagram-2B236MQR-B_Qt9zyT.js +1 -0
  120. package/dist/assets/pie-RZYD4A2V-CJR1xKEv.js +1 -0
  121. package/dist/assets/pieDiagram-ENE6RG2P-CRQdSuwS.js +39 -0
  122. package/dist/assets/plus-BwgE-Zwf.js +1 -0
  123. package/dist/assets/popover-Btp7KVc3.js +1 -0
  124. package/dist/assets/preload-helper-D4M6sveU.js +1 -0
  125. package/dist/assets/provider-models-BoUhFo9R.js +1 -0
  126. package/dist/assets/provider-scoped-model-input-DZMtoAJG.js +1 -0
  127. package/dist/assets/providers-config-page-DfiWveEF.js +1 -0
  128. package/dist/assets/quadrantDiagram-ABIIQ3AL-D5kPpttq.js +7 -0
  129. package/dist/assets/radar-I7S5WNFK-CDg8Z-1r.js +1 -0
  130. package/dist/assets/railroad-3IZDKUUU-BHnNmjxO.js +1 -0
  131. package/dist/assets/railroad-abnf-AHOZXSZD-DGtNcusW.js +1 -0
  132. package/dist/assets/railroad-ebnf-EBAXGLYW-BRFtsQmm.js +1 -0
  133. package/dist/assets/railroad-peg-LSFZ7HO6-Bj5IypCA.js +1 -0
  134. package/dist/assets/railroadDiagram-RFXS5EU6-DGh_TGlQ.js +1 -0
  135. package/dist/assets/react-BY-U3h3t.js +8 -0
  136. package/dist/assets/{refresh-cw-CILd1vAP.js → refresh-cw-Cme5qFej.js} +1 -1
  137. package/dist/assets/remote-BaVECEZw.js +1 -0
  138. package/dist/assets/requirementDiagram-TGXJPOKE-EmFph0H_.js +84 -0
  139. package/dist/assets/rotate-cw-CMRZxIkH.js +1 -0
  140. package/dist/assets/rough.esm-wJ1d3nQl.js +1 -0
  141. package/dist/assets/runtime-config-page-wygwvMB4.js +1 -0
  142. package/dist/assets/sankeyDiagram-HTMAVEWB-BXWB5Ugi.js +40 -0
  143. package/dist/assets/{save-ByJqcPTP.js → save-DNg2yJTS.js} +1 -1
  144. package/dist/assets/{search-DVIV6pfD.js → search-3ykfrAFt.js} +1 -1
  145. package/dist/assets/search-config-page-DrVKa_Ls.js +1 -0
  146. package/dist/assets/secrets-config-page-B39uQ5kF.js +3 -0
  147. package/dist/assets/security-config-BPzet76z.js +1 -0
  148. package/dist/assets/select-B5EecXJt.js +1 -0
  149. package/dist/assets/sequenceDiagram-DBY2YBRQ-ClKnO7v0.js +162 -0
  150. package/dist/assets/setting-row-Qv1Ps79v.js +1 -0
  151. package/dist/assets/{settings-2-CH5shGg_.js → settings-2-D-3um9Az.js} +1 -1
  152. package/dist/assets/sizeCapture-X5ZJPWSS-DSo-t5ou.js +1 -0
  153. package/dist/assets/{skeleton-DeX1FKw7.js → skeleton-BgkzQd4U.js} +1 -1
  154. package/dist/assets/src-Dad2ZbCt.js +1 -0
  155. package/dist/assets/stateDiagram-2N3HPSRC-BvAalBfz.js +1 -0
  156. package/dist/assets/stateDiagram-v2-6OUMAXLB-CfMU38yc.js +1 -0
  157. package/dist/assets/swimlanes-5IMT3BWC-DdVqVa1a.js +2 -0
  158. package/dist/assets/swimlanesDiagram-G3AALYLV-D9Zb9X57.js +8 -0
  159. package/dist/assets/tabs-BVD8gp3y.js +1 -0
  160. package/dist/assets/tag-chip-0qn-f3Bo.js +1 -0
  161. package/dist/assets/timeline-definition-FHXFAJF6-D4ar8IQY.js +120 -0
  162. package/dist/assets/trash-2-cnsMppRC.js +1 -0
  163. package/dist/assets/treeView-QDETBFTQ-Uy7M0WvH.js +1 -0
  164. package/dist/assets/treemap-6X3UGDF4-BJfGV7KX.js +1 -0
  165. package/dist/assets/use-config-D6yx0lb0.js +1 -0
  166. package/dist/assets/use-confirm-dialog-C_o0eQeH.js +1 -0
  167. package/dist/assets/use-viewport-layout-BWav4KCL.js +1 -0
  168. package/dist/assets/vennDiagram-L72KCM5P-CDXJ_i3p.js +34 -0
  169. package/dist/assets/wardley-OPB4EBWU-q263iF3K.js +1 -0
  170. package/dist/assets/wardleyDiagram-EHGQE667-D-dSuT_7.js +78 -0
  171. package/dist/assets/x-B3o1dWR5.js +1 -0
  172. package/dist/assets/xlsx-DUqJhg5u.js +64 -0
  173. package/dist/assets/xychartDiagram-FW5EYKEG-C9JCzsZF.js +7 -0
  174. package/dist/index.html +35 -28
  175. package/package.json +11 -6
  176. package/src/features/chat/components/chat-popover-content.tsx +1 -0
  177. package/src/features/chat/components/conversation/__tests__/chat-conversation-content.test.tsx +13 -0
  178. package/src/features/chat/components/conversation/__tests__/chat-conversation-header-section.test.tsx +21 -11
  179. package/src/features/chat/components/conversation/__tests__/chat-conversation-header.test.tsx +2 -2
  180. package/src/features/chat/components/conversation/__tests__/chat-conversation-workspace-section.test.tsx +28 -1
  181. package/src/features/chat/components/conversation/chat-conversation-content.tsx +5 -4
  182. package/src/features/chat/components/conversation/chat-conversation-header-section.tsx +9 -18
  183. package/src/features/chat/components/conversation/chat-conversation-track.tsx +35 -0
  184. package/src/features/chat/components/conversation/chat-conversation-workspace-section.tsx +2 -0
  185. package/src/features/chat/components/layout/chat-sidebar-toolbar.tsx +7 -7
  186. package/src/features/chat/components/layout/chat-sidebar-utility-menu.tsx +3 -14
  187. package/src/features/chat/features/conversation/components/__tests__/session-conversation-input.streaming.test.tsx +20 -1
  188. package/src/features/chat/features/conversation/components/session-conversation-input.tsx +97 -65
  189. package/src/features/chat/features/input/input-surface-plugins/__tests__/slash-command-plugin.test.ts +43 -0
  190. package/src/features/chat/features/input/input-surface-plugins/slash-command-plugin.utils.ts +14 -10
  191. package/src/features/chat/features/input/utils/__tests__/chat-input-bar.utils.test.ts +51 -0
  192. package/src/features/chat/features/input/utils/chat-input-bar.utils.ts +61 -21
  193. package/src/features/chat/features/message/components/__tests__/chat-message-list.container.test.tsx +42 -10
  194. package/src/features/chat/features/message/components/chat-inline-file-preview.tsx +80 -0
  195. package/src/features/chat/features/message/components/chat-message-list.container.tsx +66 -56
  196. package/src/features/chat/features/message/utils/__tests__/chat-message-file-operation.utils.test.ts +30 -0
  197. package/src/features/chat/features/message/utils/chat-message-texts.utils.ts +2 -0
  198. package/src/features/chat/features/session/components/__tests__/chat-sidebar-session-item.test.tsx +37 -0
  199. package/src/features/chat/features/session/components/chat-sidebar-project-groups.tsx +4 -3
  200. package/src/features/chat/features/session/components/chat-sidebar-session-item.tsx +3 -8
  201. package/src/features/chat/features/session/components/session-header/__tests__/chat-session-header-actions.test.tsx +65 -20
  202. package/src/features/chat/features/session/components/session-header/chat-session-header-actions.tsx +26 -28
  203. package/src/features/chat/features/session/components/session-header/chat-session-project-badge.tsx +3 -3
  204. package/src/features/chat/features/session/components/session-header/chat-session-title-switcher.tsx +3 -3
  205. package/src/features/chat/features/welcome/components/chat-welcome-project-picker.tsx +3 -3
  206. package/src/features/chat/features/welcome/components/chat-welcome-session-type-picker.tsx +3 -3
  207. package/src/features/chat/features/workspace/components/__tests__/chat-session-workspace-file-preview.test.tsx +424 -129
  208. package/src/features/chat/features/workspace/components/__tests__/chat-session-workspace-panel-content.test.tsx +113 -2
  209. package/src/features/chat/features/workspace/components/__tests__/chat-session-workspace-panel.test.tsx +3 -1
  210. package/src/features/chat/features/workspace/components/chat-session-workspace-directory-browser.tsx +197 -44
  211. package/src/features/chat/features/workspace/components/chat-session-workspace-file-breadcrumb-browser.tsx +9 -3
  212. package/src/features/chat/features/workspace/components/chat-session-workspace-file-breadcrumbs.tsx +13 -22
  213. package/src/features/chat/features/workspace/components/chat-session-workspace-file-preview.tsx +96 -194
  214. package/src/features/chat/features/workspace/components/chat-session-workspace-panel-content.tsx +239 -1
  215. package/src/features/chat/features/workspace/components/chat-session-workspace-panel-nav.tsx +61 -7
  216. package/src/features/chat/features/workspace/components/chat-session-workspace-panel.tsx +34 -8
  217. package/src/features/chat/features/workspace/components/file-content-preview/use-workspace-file-buffer.ts +42 -0
  218. package/src/features/chat/features/workspace/components/file-content-preview/workspace-document-preview-state.tsx +18 -0
  219. package/src/features/chat/features/workspace/components/file-content-preview/workspace-docx-preview.css +89 -0
  220. package/src/features/chat/features/workspace/components/file-content-preview/workspace-docx-preview.tsx +87 -0
  221. package/src/features/chat/features/workspace/components/file-content-preview/workspace-presentation-preview.tsx +82 -0
  222. package/src/features/chat/features/workspace/components/file-content-preview/workspace-spreadsheet-preview.tsx +180 -0
  223. package/src/features/chat/features/workspace/components/workspace-file-content-preview.tsx +191 -0
  224. package/src/features/chat/features/workspace/hooks/use-chat-conversation-workspace-state.ts +1 -4
  225. package/src/features/chat/features/workspace/utils/__tests__/chat-workspace-panel-view-model.utils.test.ts +157 -13
  226. package/src/features/chat/features/workspace/utils/chat-thread-workspace-session.utils.ts +151 -2
  227. package/src/features/chat/features/workspace/utils/chat-workspace-file-tab-persistence.utils.ts +17 -0
  228. package/src/features/chat/features/workspace/utils/chat-workspace-file-tab.utils.ts +66 -0
  229. package/src/features/chat/features/workspace/utils/chat-workspace-file-viewer.utils.ts +49 -0
  230. package/src/features/chat/features/workspace/utils/chat-workspace-panel-layout.utils.ts +13 -0
  231. package/src/features/chat/features/workspace/utils/chat-workspace-panel-view-model.utils.ts +196 -43
  232. package/src/features/chat/index.ts +1 -0
  233. package/src/features/chat/managers/__tests__/chat-thread-workspace.manager.test.ts +101 -0
  234. package/src/features/chat/managers/__tests__/chat-thread.manager.test.ts +48 -60
  235. package/src/features/chat/managers/chat-session-list.manager.ts +1 -0
  236. package/src/features/chat/managers/chat-thread.manager.ts +137 -162
  237. package/src/features/chat/pages/ncp-chat-page.tsx +1 -2
  238. package/src/features/chat/stores/__tests__/chat-thread.store.test.ts +87 -1
  239. package/src/features/chat/stores/chat-message-layout.store.test.ts +37 -0
  240. package/src/features/chat/stores/chat-message-layout.store.ts +41 -0
  241. package/src/features/chat/stores/chat-thread.store.ts +77 -40
  242. package/src/features/chat/types/chat-input-bar.types.ts +2 -0
  243. package/src/features/settings/pages/__tests__/appearance-settings-page.test.tsx +25 -8
  244. package/src/features/settings/pages/appearance-settings-page.tsx +92 -13
  245. package/src/index.css +36 -2
  246. package/src/shared/components/__tests__/file-type-icon.test.tsx +61 -0
  247. package/src/shared/components/common/__tests__/brand-header.test.tsx +17 -7
  248. package/src/shared/components/common/brand-header.tsx +34 -34
  249. package/src/shared/components/doc-browser/__tests__/doc-browser-context.test.tsx +19 -0
  250. package/src/shared/components/doc-browser/__tests__/doc-browser.test.tsx +16 -0
  251. package/src/shared/components/doc-browser/doc-browser-context.tsx +1 -0
  252. package/src/shared/components/doc-browser/doc-browser.tsx +11 -3
  253. package/src/shared/components/doc-browser/managers/doc-browser.manager.ts +5 -0
  254. package/src/shared/components/doc-browser/stores/doc-browser.store.ts +6 -1
  255. package/src/shared/components/doc-browser/types/doc-browser.types.ts +2 -0
  256. package/src/shared/components/doc-browser/utils/doc-browser-state.utils.ts +11 -0
  257. package/src/shared/components/file-type-icon.tsx +272 -0
  258. package/src/shared/components/resizable-right-panel/__tests__/resizable-right-panel.test.tsx +22 -0
  259. package/src/shared/components/resizable-right-panel/resizable-right-panel.tsx +17 -2
  260. package/src/shared/components/ui/tab-strip/__tests__/compact-tab-strip.test.tsx +38 -0
  261. package/src/shared/components/ui/tab-strip/compact-tab-strip.tsx +82 -2
  262. package/src/shared/hooks/use-server-path-read.ts +10 -5
  263. package/src/shared/lib/api/__tests__/server-path.utils.test.ts +28 -0
  264. package/src/shared/lib/api/types.ts +3 -3
  265. package/src/shared/lib/api/utils/server-path.utils.ts +20 -20
  266. package/src/shared/lib/i18n/locales/en-US/chat.json +25 -1
  267. package/src/shared/lib/i18n/locales/en-US/core.json +6 -0
  268. package/src/shared/lib/i18n/locales/zh-CN/chat.json +25 -1
  269. package/src/shared/lib/i18n/locales/zh-CN/core.json +6 -0
  270. package/src/shared/lib/session-project/__tests__/workspace-file-breadcrumb.test.ts +1 -4
  271. package/src/shared/lib/session-project/workspace-file-breadcrumb.utils.ts +1 -18
  272. package/dist/assets/api-iNBpppzj.js +0 -13
  273. package/dist/assets/appearance-settings-page-DMffQFR3.js +0 -1
  274. package/dist/assets/channels-list-page-CCtp0BRS.js +0 -8
  275. package/dist/assets/chat-page-H5a0dWvO.js +0 -1
  276. package/dist/assets/config-split-page-CrOJgB-Y.js +0 -1
  277. package/dist/assets/confirm-dialog-Chv50USy.js +0 -5
  278. package/dist/assets/desktop-update-config-KzTuXRf_.js +0 -1
  279. package/dist/assets/dist-BunzmC4B.js +0 -1
  280. package/dist/assets/dist-ChmGwvtK.js +0 -1
  281. package/dist/assets/doc-browser-DNRL4Zvd.js +0 -1
  282. package/dist/assets/doc-browser-Dg0pmkOg.js +0 -1
  283. package/dist/assets/doc-browser-context-BHY-0FSD.js +0 -1
  284. package/dist/assets/es2015-DQZ5JWwK.js +0 -41
  285. package/dist/assets/i18n-provider-Cu8XyZb_.js +0 -1
  286. package/dist/assets/index-CzfBdvK1.css +0 -1
  287. package/dist/assets/index-DGqHne1Q.js +0 -104
  288. package/dist/assets/loader-circle-CDSFuzmk.js +0 -1
  289. package/dist/assets/mcp-marketplace-page-BeEQK0DL.js +0 -9
  290. package/dist/assets/mcp-marketplace-page-D6idOTLp.js +0 -1
  291. package/dist/assets/model-config-page-bNNo-XyG.js +0 -1
  292. package/dist/assets/plus-D4DsdLN-.js +0 -1
  293. package/dist/assets/provider-scoped-model-input-IlgQCsoT.js +0 -1
  294. package/dist/assets/providers-config-page-wLo6CAJw.js +0 -1
  295. package/dist/assets/react-BPIsQIlO.js +0 -8
  296. package/dist/assets/remote-uKEYp7AR.js +0 -1
  297. package/dist/assets/rotate-cw-wPFPg1kH.js +0 -1
  298. package/dist/assets/runtime-config-page-DDn1sP4G.js +0 -1
  299. package/dist/assets/search-config-page-B-Cf4iiB.js +0 -1
  300. package/dist/assets/secrets-config-page-DPKsY8Iw.js +0 -3
  301. package/dist/assets/security-config-e9SSBlKr.js +0 -1
  302. package/dist/assets/select-BdgczSNw.js +0 -1
  303. package/dist/assets/setting-row-BkTVUTKx.js +0 -1
  304. package/dist/assets/side-dock-BsBvcJ9i.js +0 -3
  305. package/dist/assets/switch-Cv8Ekb3x.js +0 -1
  306. package/dist/assets/tag-chip-RB9uKcoC.js +0 -1
  307. package/dist/assets/tooltip-oQlEN-l6.js +0 -1
  308. package/dist/assets/use-config-l3W7y8Qz.js +0 -1
  309. package/dist/assets/use-confirm-dialog-BAxaVOJI.js +0 -1
  310. package/dist/assets/x-BBMffEN0.js +0 -1
  311. package/src/features/chat/components/layout/__tests__/chat-sidebar-utility-menu.test.tsx +0 -55
  312. /package/dist/assets/{config-hints-C_iP1zl_.js → config-hints-LxG80Uj5.js} +0 -0
  313. /package/dist/assets/{host-capabilities-9bB_998Z.js → host-capabilities-CFbAfrrA.js} +0 -0
  314. /package/dist/assets/{middleware-BILWbJE4.js → middleware-CAXxl-af.js} +0 -0
@@ -19,6 +19,7 @@ const captures = vi.hoisted(() => ({
19
19
  openFilePreview: vi.fn(),
20
20
  handleToolAction: vi.fn(),
21
21
  showContent: vi.fn(),
22
+ filePreviewProps: [] as Array<{ showBreadcrumbs?: boolean }>,
22
23
  }));
23
24
 
24
25
  vi.mock("@nextclaw/agent-chat-ui", async (importOriginal) => {
@@ -61,12 +62,25 @@ vi.mock("@/shared/lib/i18n", () => ({
61
62
  t: (key: string) => key,
62
63
  }));
63
64
 
65
+ vi.mock(
66
+ "@/features/chat/features/workspace/components/chat-session-workspace-file-preview",
67
+ () => ({
68
+ ChatSessionWorkspaceFilePreview: (props: {
69
+ showBreadcrumbs?: boolean;
70
+ }) => {
71
+ captures.filePreviewProps.push(props);
72
+ return <div data-testid="inline-workspace-file-preview" />;
73
+ },
74
+ }),
75
+ );
76
+
64
77
  beforeEach(() => {
65
78
  captures.renders = [];
66
79
  captures.language = "en";
67
80
  captures.openFilePreview.mockReset();
68
81
  captures.handleToolAction.mockReset();
69
82
  captures.showContent.mockReset();
83
+ captures.filePreviewProps = [];
70
84
  });
71
85
 
72
86
  it("reuses adapted message references when the source message object is unchanged", () => {
@@ -484,22 +498,40 @@ it("passes the inline panel app renderer to the shared chat UI", () => {
484
498
  expect(captures.renders[captures.renders.length - 1]?.renderPanelAppCard).toEqual(expect.any(Function));
485
499
  });
486
500
 
487
- it("passes the inline display renderer to the shared chat UI", () => {
501
+ it("renders inline file displays through the workspace file preview", () => {
488
502
  render(<ChatMessageListContainer messages={[]} isSending={false} />);
489
503
 
490
504
  const renderInlineDisplay =
491
505
  captures.renders[captures.renders.length - 1]?.renderInlineDisplay;
492
506
 
493
507
  expect(renderInlineDisplay).toEqual(expect.any(Function));
494
- expect(
495
- renderInlineDisplay?.({
496
- target: {
497
- type: "file",
498
- payload: { path: "README.md" },
499
- },
500
- title: "README",
501
- }),
502
- ).toBeUndefined();
508
+ const rendered = renderInlineDisplay?.({
509
+ target: {
510
+ type: "file",
511
+ payload: { path: "preview.html", viewer: "rendered" },
512
+ },
513
+ title: "Preview",
514
+ description: "Rendered HTML",
515
+ });
516
+
517
+ if (!isValidElement<{ display?: unknown }>(rendered)) {
518
+ throw new Error("Expected inline file renderer to return a React element");
519
+ }
520
+ expect(rendered.props.display).toEqual({
521
+ target: {
522
+ type: "file",
523
+ payload: { path: "preview.html", viewer: "rendered" },
524
+ },
525
+ title: "Preview",
526
+ description: "Rendered HTML",
527
+ });
528
+
529
+ render(rendered);
530
+
531
+ expect(screen.getByText("Preview")).toBeTruthy();
532
+ expect(screen.getByText("preview.html")).toBeTruthy();
533
+ expect(screen.queryByText("Rendered HTML")).toBeNull();
534
+ expect(captures.filePreviewProps.at(-1)?.showBreadcrumbs).toBe(false);
503
535
  });
504
536
 
505
537
  it("keeps inline panel app displays expandable from the card header", () => {
@@ -0,0 +1,80 @@
1
+ import type {
2
+ ChatFileOpenActionViewModel,
3
+ ChatInlineDisplayViewModel,
4
+ } from "@nextclaw/agent-chat-ui";
5
+ import { FileText } from "lucide-react";
6
+ import { ChatSessionWorkspaceFilePreview } from "@/features/chat/features/workspace/components/chat-session-workspace-file-preview";
7
+ import { createWorkspaceFileTab } from "@/features/chat/features/workspace/utils/chat-workspace-file-tab.utils";
8
+
9
+ type ChatInlineFilePreviewProps = {
10
+ display: ChatInlineDisplayViewModel;
11
+ parentSessionKey: string | null;
12
+ sessionProjectRoot: string | null;
13
+ sessionWorkingDir: string | null;
14
+ onFileOpen: (action: ChatFileOpenActionViewModel) => void;
15
+ };
16
+
17
+ function readFileName(path: string): string {
18
+ return path.split(/[\\/]/).filter(Boolean).pop() ?? path;
19
+ }
20
+
21
+ export function ChatInlineFilePreview({
22
+ display,
23
+ parentSessionKey,
24
+ sessionProjectRoot,
25
+ sessionWorkingDir,
26
+ onFileOpen,
27
+ }: ChatInlineFilePreviewProps) {
28
+ if (display.target.type !== "file") {
29
+ return null;
30
+ }
31
+ const { path, line, column, viewer } = display.target.payload;
32
+ const file = createWorkspaceFileTab(
33
+ {
34
+ path,
35
+ label: display.title,
36
+ viewMode: "preview",
37
+ previewViewer: viewer,
38
+ line,
39
+ column,
40
+ },
41
+ parentSessionKey,
42
+ );
43
+ if (!file) {
44
+ return null;
45
+ }
46
+ const title = display.title ?? readFileName(file.path);
47
+
48
+ return (
49
+ <section
50
+ className="my-2 w-full max-w-[48rem] overflow-hidden rounded-lg border border-border bg-card shadow-sm"
51
+ data-chat-inline-file-preview="true"
52
+ data-chat-message-wide-content="true"
53
+ >
54
+ <header className="border-b border-border bg-muted/45 px-3 py-2">
55
+ <div className="flex min-w-0 items-center gap-2 text-xs font-medium text-foreground">
56
+ <FileText
57
+ className="h-3.5 w-3.5 shrink-0 text-muted-foreground"
58
+ aria-hidden
59
+ />
60
+ <span className="truncate">{title}</span>
61
+ </div>
62
+ <p
63
+ className="mt-1 truncate font-mono text-[11px] font-normal text-muted-foreground"
64
+ title={file.path}
65
+ >
66
+ {file.path}
67
+ </p>
68
+ </header>
69
+ <div className="h-[420px] min-h-[240px] max-h-[min(60vh,420px)] overflow-hidden">
70
+ <ChatSessionWorkspaceFilePreview
71
+ file={file}
72
+ sessionProjectRoot={sessionProjectRoot}
73
+ sessionWorkingDir={sessionWorkingDir}
74
+ showBreadcrumbs={false}
75
+ onFileOpen={onFileOpen}
76
+ />
77
+ </div>
78
+ </section>
79
+ );
80
+ }
@@ -4,13 +4,11 @@ import {
4
4
  type ChatInlineDisplayViewModel,
5
5
  type ChatInlineTokenViewModel,
6
6
  type ChatMessageViewModel,
7
+ type ChatPanelAppCardViewModel,
7
8
  ChatMessageList,
8
9
  } from "@nextclaw/agent-chat-ui";
9
10
  import { usePresenter } from "@/features/chat/components/providers/chat-presenter.provider";
10
- import {
11
- adaptChatMessage,
12
- type ChatMessageSource,
13
- } from "@/features/chat/features/message/utils/chat-message.utils";
11
+ import { adaptChatMessage, type ChatMessageSource } from "@/features/chat/features/message/utils/chat-message.utils";
14
12
  import { buildChatMessageProcessSummary } from "@/features/chat/features/message/utils/chat-message-process-summary.utils";
15
13
  import {
16
14
  buildChatMessageAdapterTexts,
@@ -23,9 +21,14 @@ import {
23
21
  type ContextCompactionTimelineView,
24
22
  } from "@/features/chat/features/session/utils/ncp-session-context-metadata.utils";
25
23
  import { AgentIdentityAvatar } from "@/shared/components/common/agent-identity";
24
+ import { ChatInlineFilePreview } from "@/features/chat/features/message/components/chat-inline-file-preview";
26
25
  import { ChatInlinePanelAppCard } from "@/features/chat/features/message/components/chat-inline-panel-app-card";
27
26
  import { useChatQueryStore } from "@/features/chat/stores/ncp-chat-query.store";
27
+ import { useChatSessionListStore } from "@/features/chat/stores/chat-session-list.store";
28
+ import { useChatMessageLayoutStore } from "@/features/chat/stores/chat-message-layout.store";
29
+ import { useNcpChatSelectedSession } from "@/features/chat/features/ncp/hooks/use-ncp-chat-derived-state";
28
30
  import { useI18n } from "@/app/components/i18n-provider";
31
+ import { buildServerPathContentUrl } from "@/shared/lib/api";
29
32
  import { formatDateTime, t } from "@/shared/lib/i18n";
30
33
 
31
34
  type ChatMessageListContainerProps = {
@@ -59,6 +62,9 @@ function renderChatInlineDisplay(display: ChatInlineDisplayViewModel) {
59
62
  );
60
63
  }
61
64
 
65
+ const renderChatToolAgent = (agentId: string) => <AgentIdentityAvatar agentId={agentId} className="h-4 w-4 shrink-0" />;
66
+ const renderChatPanelAppCard = (panelApp: ChatPanelAppCardViewModel) => <ChatInlinePanelAppCard panelApp={panelApp} />;
67
+
62
68
  type ContextInheritanceTimelineView = {
63
69
  sourceSessionId: string;
64
70
  inheritedMessageCount: number;
@@ -85,11 +91,7 @@ type ChatTimelineItem =
85
91
  inheritance: ContextInheritanceTimelineView;
86
92
  };
87
93
 
88
- function ChatContextCompactionDivider({
89
- checkpoint,
90
- }: {
91
- checkpoint: ContextCompactionTimelineView;
92
- }) {
94
+ function ChatContextCompactionDivider({ checkpoint }: { checkpoint: ContextCompactionTimelineView }) {
93
95
  const title = [
94
96
  `${t("chatContextCompactionCoveredMessages")}: ${checkpoint.coveredSessionMessageCount}`,
95
97
  `${t("chatContextCompactionOriginalTokens")}: ${checkpoint.originalEstimatedTokens}`,
@@ -115,11 +117,7 @@ function ChatContextCompactionDivider({
115
117
  );
116
118
  }
117
119
 
118
- function ChatContextInheritanceDivider({
119
- inheritance,
120
- }: {
121
- inheritance: ContextInheritanceTimelineView;
122
- }) {
120
+ function ChatContextInheritanceDivider({ inheritance }: { inheritance: ContextInheritanceTimelineView }) {
123
121
  const title = [
124
122
  `${t("chatContextInheritanceSourceSession")}: ${inheritance.sourceSessionId}`,
125
123
  `${t("chatContextInheritanceMessages")}: ${inheritance.inheritedMessageCount}`,
@@ -141,20 +139,12 @@ function resolveCompactionBoundaryIndex(params: {
141
139
  visibleRawMessages: readonly NcpMessage[];
142
140
  rawMessageId: string;
143
141
  }): number {
144
- const {
145
- rawMessageId,
146
- rawMessages,
147
- visibleRawMessages,
148
- } = params;
149
- const physicalIndex = rawMessages.findIndex(
150
- (message) => message.id === rawMessageId,
151
- );
142
+ const { rawMessageId, rawMessages, visibleRawMessages } = params;
143
+ const physicalIndex = rawMessages.findIndex((message) => message.id === rawMessageId);
152
144
  if (physicalIndex < 0) {
153
145
  return visibleRawMessages.length - 1;
154
146
  }
155
- const previousVisibleCount = rawMessages
156
- .slice(0, physicalIndex)
157
- .filter(isVisibleChatMessage).length;
147
+ const previousVisibleCount = rawMessages.slice(0, physicalIndex).filter(isVisibleChatMessage).length;
158
148
  return previousVisibleCount - 1;
159
149
  }
160
150
 
@@ -167,9 +157,7 @@ function isVisibleChatMessage(message: NcpMessage): boolean {
167
157
  return !readContextCompactionTimeline(message) && !readInheritedSourceSessionId(message);
168
158
  }
169
159
 
170
- function resolveContextInheritanceBoundary(
171
- messages: readonly NcpMessage[],
172
- ): ContextInheritanceTimelineBoundary | null {
160
+ function resolveContextInheritanceBoundary(messages: readonly NcpMessage[]): ContextInheritanceTimelineBoundary | null {
173
161
  const boundaryIndex = messages.findIndex((message) => readInheritedSourceSessionId(message));
174
162
  if (boundaryIndex < 0) {
175
163
  return null;
@@ -181,7 +169,8 @@ function resolveContextInheritanceBoundary(
181
169
  return {
182
170
  boundaryIndex: messages.slice(0, boundaryIndex).filter(isVisibleChatMessage).length,
183
171
  sourceSessionId,
184
- inheritedMessageCount: messages.filter((message) => readInheritedSourceSessionId(message) === sourceSessionId).length,
172
+ inheritedMessageCount: messages.filter((message) => readInheritedSourceSessionId(message) === sourceSessionId)
173
+ .length,
185
174
  };
186
175
  }
187
176
 
@@ -196,8 +185,12 @@ function buildTimelineItems(params: {
196
185
  checkpoint: readContextCompactionTimeline(message),
197
186
  }))
198
187
  .filter(
199
- (entry): entry is { rawMessageId: string; checkpoint: ContextCompactionTimelineView } =>
200
- Boolean(entry.checkpoint),
188
+ (
189
+ entry,
190
+ ): entry is {
191
+ rawMessageId: string;
192
+ checkpoint: ContextCompactionTimelineView;
193
+ } => Boolean(entry.checkpoint),
201
194
  )
202
195
  .map((entry) => ({
203
196
  key: entry.rawMessageId,
@@ -286,10 +279,36 @@ export function ChatMessageListContainer({
286
279
  }: ChatMessageListContainerProps) {
287
280
  const presenter = usePresenter();
288
281
  const { language } = useI18n();
289
- const texts = useMemo(
290
- () => buildChatMessageAdapterTexts(language),
291
- [language],
282
+ const messageLayout = useChatMessageLayoutStore((state) => state.layout);
283
+ const selectedSessionKey = useChatSessionListStore((state) => state.snapshot.selectedSessionKey);
284
+ const selectedSession = useNcpChatSelectedSession(selectedSessionKey);
285
+ const localFileBasePath = selectedSession?.workingDir ?? selectedSession?.projectRoot ?? null;
286
+ const renderInlineDisplayWithFiles = useCallback(
287
+ (display: ChatInlineDisplayViewModel) => {
288
+ const panelAppDisplay = renderChatInlineDisplay(display);
289
+ if (panelAppDisplay) {
290
+ return panelAppDisplay;
291
+ }
292
+ if (display.target.type === "file") {
293
+ return (
294
+ <ChatInlineFilePreview
295
+ display={display}
296
+ parentSessionKey={selectedSessionKey}
297
+ sessionProjectRoot={selectedSession?.projectRoot ?? null}
298
+ sessionWorkingDir={localFileBasePath}
299
+ onFileOpen={presenter.chatThreadManager.openFilePreview}
300
+ />
301
+ );
302
+ }
303
+ return undefined;
304
+ },
305
+ [localFileBasePath, presenter.chatThreadManager, selectedSession?.projectRoot, selectedSessionKey],
306
+ );
307
+ const resolveFileContentUrl = useCallback(
308
+ (action: { path: string }) => buildServerPathContentUrl(action.path, localFileBasePath),
309
+ [localFileBasePath],
292
310
  );
311
+ const texts = useMemo(() => buildChatMessageAdapterTexts(language), [language]);
293
312
 
294
313
  const messages = useMemo(() => {
295
314
  const visibleRawMessages = rawMessages.filter(isVisibleChatMessage);
@@ -322,7 +341,11 @@ export function ChatMessageListContainer({
322
341
  texts,
323
342
  });
324
343
 
325
- messageViewModelCache.set(message, { language, processSummaryLabel, viewModel });
344
+ messageViewModelCache.set(message, {
345
+ language,
346
+ processSummaryLabel,
347
+ viewModel,
348
+ });
326
349
  return [viewModel];
327
350
  });
328
351
  }, [language, rawMessages, texts]);
@@ -330,20 +353,12 @@ export function ChatMessageListContainer({
330
353
  const hasAssistantDraft = useMemo(
331
354
  () =>
332
355
  messages.some(
333
- (message) =>
334
- message.role === "assistant" &&
335
- (message.status === "streaming" || message.status === "pending"),
356
+ (message) => message.role === "assistant" && (message.status === "streaming" || message.status === "pending"),
336
357
  ),
337
358
  [messages],
338
359
  );
339
- const messageTexts = useMemo(
340
- () => buildChatMessageTexts(language),
341
- [language],
342
- );
343
- const timelineItems = useMemo(
344
- () => buildTimelineItems({ rawMessages, messages }),
345
- [messages, rawMessages],
346
- );
360
+ const messageTexts = useMemo(() => buildChatMessageTexts(language), [language]);
361
+ const timelineItems = useMemo(() => buildTimelineItems({ rawMessages, messages }), [messages, rawMessages]);
347
362
  const sessionSkillsQuery = useChatQueryStore((state) => state.snapshot.sessionSkillsQuery);
348
363
  const handleInlineTokenClick = useCallback(
349
364
  (token: ChatInlineTokenViewModel) => {
@@ -396,6 +411,7 @@ export function ChatMessageListContainer({
396
411
  ) : (
397
412
  <ChatMessageList
398
413
  key={item.key}
414
+ layout={messageLayout}
399
415
  messages={item.messages}
400
416
  isSending={index === timelineItems.length - 1 ? isSending : false}
401
417
  hasAssistantDraft={hasAssistantDraft}
@@ -404,16 +420,10 @@ export function ChatMessageListContainer({
404
420
  onFileOpen={presenter.chatThreadManager.openFilePreview}
405
421
  onAttachmentOpen={handleAttachmentOpen}
406
422
  onInlineTokenClick={handleInlineTokenClick}
407
- renderInlineDisplay={renderChatInlineDisplay}
408
- renderToolAgent={(agentId) => (
409
- <AgentIdentityAvatar
410
- agentId={agentId}
411
- className="h-4 w-4 shrink-0"
412
- />
413
- )}
414
- renderPanelAppCard={(panelApp) => (
415
- <ChatInlinePanelAppCard panelApp={panelApp} />
416
- )}
423
+ resolveFileContentUrl={resolveFileContentUrl}
424
+ renderInlineDisplay={renderInlineDisplayWithFiles}
425
+ renderToolAgent={renderChatToolAgent}
426
+ renderPanelAppCard={renderChatPanelAppCard}
417
427
  />
418
428
  ),
419
429
  )}
@@ -63,6 +63,36 @@ it("builds edit-file previews from structured args before the tool finishes", ()
63
63
  expect(editLines[1]).not.toHaveProperty("newLineNumber");
64
64
  });
65
65
 
66
+ it("keeps incomplete edit-file arguments available before preview fields arrive", () => {
67
+ const partialArgs = '{"path":"src/app.ts","oldText":';
68
+ const adapted = adapt([
69
+ {
70
+ id: "assistant-partial-edit-input",
71
+ role: "assistant",
72
+ parts: [
73
+ {
74
+ type: "tool-invocation",
75
+ toolInvocation: {
76
+ status: ToolInvocationStatus.PARTIAL_CALL,
77
+ toolCallId: "edit-partial-1",
78
+ toolName: "edit_file",
79
+ args: partialArgs,
80
+ },
81
+ },
82
+ ],
83
+ },
84
+ ] as unknown as ChatMessageSource[]);
85
+
86
+ expect(adapted[0]?.parts[0]).toMatchObject({
87
+ type: "tool-card",
88
+ card: {
89
+ toolName: "edit_file",
90
+ input: partialArgs,
91
+ statusTone: "running",
92
+ },
93
+ });
94
+ });
95
+
66
96
  it("uses structured edit-file result line numbers after the tool finishes", () => {
67
97
  const adapted = adapt([
68
98
  {
@@ -106,6 +106,8 @@ export function buildChatMessageTexts(
106
106
  copiedCodeLabel: t("chatCodeCopied"),
107
107
  copyMessageLabel: t("chatMessageCopy"),
108
108
  copiedMessageLabel: t("chatMessageCopied"),
109
+ mermaidDiagramLabel: t("chatMermaidDiagram"),
110
+ mermaidRenderErrorLabel: t("chatMermaidRenderError"),
109
111
  typingLabel: t("chatTyping"),
110
112
  reasoningCharacterCountTemplates: {
111
113
  inProgress: t("chatReasoningInProgressCharacterCount"),
@@ -0,0 +1,37 @@
1
+ import { render, screen } from '@testing-library/react';
2
+ import { expect, it, vi } from 'vitest';
3
+ import { ChatSidebarSessionItem } from '@/features/chat/features/session/components/chat-sidebar-session-item';
4
+
5
+ it('shows session actions only on hover or when an action owns focus', () => {
6
+ render(
7
+ <ChatSidebarSessionItem
8
+ sessionKey="session:current"
9
+ active
10
+ showUnreadDot={false}
11
+ context={{ icon: null, label: null }}
12
+ isPinned={false}
13
+ title="Current Task"
14
+ previewText="Preview"
15
+ trailingText="Now"
16
+ isEditing={false}
17
+ draftLabel="Current Task"
18
+ isSaving={false}
19
+ onSelect={vi.fn()}
20
+ onStartEditing={vi.fn()}
21
+ onDraftLabelChange={vi.fn()}
22
+ onSave={vi.fn()}
23
+ onCancel={vi.fn()}
24
+ onTogglePinned={vi.fn()}
25
+ />,
26
+ );
27
+
28
+ screen.getByText('Current Task').closest('button')?.focus();
29
+ const actions = screen.getByLabelText('Pin session').parentElement;
30
+
31
+ expect(actions?.className).toContain('opacity-0');
32
+ expect(actions?.className).toContain('group-hover/session:opacity-100');
33
+ expect(actions?.className).toContain('focus-within:opacity-100');
34
+ expect(actions?.className).not.toContain(
35
+ 'group-focus-within/session:opacity-100',
36
+ );
37
+ });
@@ -2,7 +2,8 @@ import { useMemo, useState, type ReactNode } from 'react';
2
2
  import { ChevronDown, ChevronRight, Folder, Pin, Plus } from 'lucide-react';
3
3
  import { usePresenter } from '@/features/chat/components/providers/chat-presenter.provider';
4
4
  import { ChatSessionTypeMenu } from "@/features/chat/features/session-type/components/chat-session-type-menu";
5
- import { Popover, PopoverContent, PopoverTrigger } from '@/shared/components/ui/popover';
5
+ import { Popover, PopoverTrigger } from '@/shared/components/ui/popover';
6
+ import { ChatPopoverContent } from '@/features/chat/components/chat-popover-content';
6
7
  import { IconActionButton } from '@/shared/components/ui/actions/icon-action-button';
7
8
  import type { ChatSessionTypeOption } from "@/features/chat/features/session-type/utils/chat-session-type.utils";
8
9
  import type { NcpSessionListItemView } from '@/features/chat/features/ncp/hooks/use-ncp-session-list-view';
@@ -107,7 +108,7 @@ export function ChatSidebarProjectGroups(props: ChatSidebarProjectGroupsProps) {
107
108
  label={actionLabel}
108
109
  />
109
110
  </PopoverTrigger>
110
- <PopoverContent
111
+ <ChatPopoverContent
111
112
  align="end"
112
113
  className="w-56 rounded-2xl border border-border bg-popover p-1.5 text-popover-foreground shadow-[0_24px_60px_-28px_rgba(15,23,42,0.38)]"
113
114
  >
@@ -122,7 +123,7 @@ export function ChatSidebarProjectGroups(props: ChatSidebarProjectGroupsProps) {
122
123
  setOpenProjectRoot(null);
123
124
  }}
124
125
  />
125
- </PopoverContent>
126
+ </ChatPopoverContent>
126
127
  </Popover>
127
128
  ) : (
128
129
  <IconActionButton
@@ -169,12 +169,7 @@ function ChatSidebarSessionDisplayView({
169
169
  event.stopPropagation();
170
170
  onOpenChildSessions();
171
171
  }}
172
- className={cn(
173
- 'absolute right-12 top-0 inline-flex h-6 items-center gap-1 rounded-md px-1.5 text-[10px] font-medium text-muted-foreground transition-colors hover:bg-black/10 hover:text-foreground',
174
- active
175
- ? 'opacity-100'
176
- : 'opacity-0 group-hover/session:opacity-100 group-focus-within/session:opacity-100'
177
- )}
172
+ className="pointer-events-none absolute right-12 top-0 inline-flex h-6 items-center gap-1 rounded-md px-1.5 text-[10px] font-medium text-muted-foreground opacity-0 transition-colors hover:bg-black/10 hover:text-foreground group-hover/session:pointer-events-auto group-hover/session:opacity-100 focus-visible:pointer-events-auto focus-visible:opacity-100"
178
173
  aria-label={t('chatSessionOpenChildSessions')}
179
174
  title={t('chatSessionOpenChildSessions')}
180
175
  >
@@ -183,11 +178,11 @@ function ChatSidebarSessionDisplayView({
183
178
  </button>
184
179
  ) : null}
185
180
  {runStatus ? (
186
- <span className="absolute right-0 top-0 inline-flex h-6 w-6 items-center justify-center transition-opacity group-hover/session:opacity-0 group-focus-within/session:opacity-0">
181
+ <span className="absolute right-0 top-0 inline-flex h-6 w-6 items-center justify-center transition-opacity group-hover/session:opacity-0">
187
182
  <SessionRunBadge status={runStatus} />
188
183
  </span>
189
184
  ) : null}
190
- <div className="absolute right-0 top-0 flex h-6 items-center gap-0.5 opacity-0 transition-opacity group-hover/session:opacity-100 group-focus-within/session:opacity-100">
185
+ <div className="pointer-events-none absolute right-0 top-0 flex h-6 items-center gap-0.5 opacity-0 transition-opacity group-hover/session:pointer-events-auto group-hover/session:opacity-100 focus-within:pointer-events-auto focus-within:opacity-100">
191
186
  <IconActionButton
192
187
  size="sm"
193
188
  tone="strong"