@nextclaw/ui 0.15.4 → 0.15.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (300) hide show
  1. package/CHANGELOG.md +41 -0
  2. package/dist/assets/abnfDiagram-VRR7QNED-BYpK0F5F.js +1 -0
  3. package/dist/assets/{aiden0z-pptx-renderer.es-Bd99vut2.js → aiden0z-pptx-renderer.es-D87ywFdC.js} +1 -1
  4. package/dist/assets/api-Dyb5rgh0.js +13 -0
  5. package/dist/assets/app-presenter-provider-7jV0nPiL.js +106 -0
  6. package/dist/assets/{appearance-settings-page-fLIJbx1C.js → appearance-settings-page-DFm3BbEb.js} +1 -1
  7. package/dist/assets/arc-DFELHWM2.js +1 -0
  8. package/dist/assets/architecture-TIHT7OUA-ClwVFq-K.js +1 -0
  9. package/dist/assets/architectureDiagram-ZJ3FMSHR-CvRhdovL.js +36 -0
  10. package/dist/assets/array-BqYdCIE6.js +1 -0
  11. package/dist/assets/blockDiagram-677ZJIJ3-DVYNkbR_.js +132 -0
  12. package/dist/assets/{book-open-DWUjS6aW.js → book-open-L6LCuqOr.js} +1 -1
  13. package/dist/assets/c4Diagram-LMCZKHZV-B1w-iwRe.js +10 -0
  14. package/dist/assets/channel-BEHOSFc5.js +1 -0
  15. package/dist/assets/{channels-list-page-CvCTJraa.js → channels-list-page-DOaPKNJA.js} +2 -2
  16. package/dist/assets/{chat-page-B42UUIFR.js → chat-page-Cw9uDhD_.js} +1 -1
  17. package/dist/assets/chunk-2Q5K7J3B-DrIhesSX.js +1 -0
  18. package/dist/assets/chunk-32BRIVSS-kG3lg0bC.js +1 -0
  19. package/dist/assets/chunk-52WLFC77-BLeB7iGk.js +10 -0
  20. package/dist/assets/chunk-5FCAYU7R-BQga1Rr7.js +1 -0
  21. package/dist/assets/chunk-5HE753X5-DU81hkcF.js +1 -0
  22. package/dist/assets/chunk-5JV3BV7I-C2bAx0m7.js +1 -0
  23. package/dist/assets/chunk-5TONJI2A-BExIIgWY.js +2 -0
  24. package/dist/assets/chunk-5VM5RSS4-C3XB05yh.js +15 -0
  25. package/dist/assets/chunk-7BUUIJ7U-JiyRtrGu.js +1 -0
  26. package/dist/assets/chunk-BIQX33UG-BZRV20pI.js +1 -0
  27. package/dist/assets/chunk-C7G6YPKG-AsyDSQ9j.js +1 -0
  28. package/dist/assets/chunk-CQNSW5MT-iN-fiXVA.js +1 -0
  29. package/dist/assets/chunk-CYSBUYHQ-DhC_TRzI.js +1 -0
  30. package/dist/assets/chunk-EMLP6XTP-QFLK8R6d.js +1 -0
  31. package/dist/assets/chunk-EX3LRPZG-DzUwhkCz.js +231 -0
  32. package/dist/assets/chunk-FWX5IMBZ-DFelpZBy.js +2 -0
  33. package/dist/assets/chunk-HOUHSVGY-BMPwFEwN.js +1 -0
  34. package/dist/assets/chunk-ICXQ74PX-DH8NJMok.js +2 -0
  35. package/dist/assets/chunk-JG7HCLWE-D-95AsO4.js +2 -0
  36. package/dist/assets/chunk-JWPE2WC7-DIaTv8kx.js +1 -0
  37. package/dist/assets/chunk-KEIR6QF5-A3jEqwzn.js +161 -0
  38. package/dist/assets/chunk-MOJQB5TN-BPFLzWgj.js +88 -0
  39. package/dist/assets/chunk-MOZMSUNE-DR_fzxG0.js +1 -0
  40. package/dist/assets/chunk-OGEWGWER-TIcEoPPH.js +1 -0
  41. package/dist/assets/chunk-OSBZ3O6U-BALNvSfG.js +1 -0
  42. package/dist/assets/chunk-PUDLZKDR-I-EXhf_k.js +156 -0
  43. package/dist/assets/chunk-Q4XR5HBZ-uvIGstBS.js +70 -0
  44. package/dist/assets/chunk-QBLGF6JB-r-TjLdxa.js +1 -0
  45. package/dist/assets/chunk-R7FJI6CG-N-1LDgvy.js +1 -0
  46. package/dist/assets/chunk-RYQCIY6F-jxNASx65.js +1 -0
  47. package/dist/assets/chunk-U6XO7XAA-Cphbuc7q.js +2 -0
  48. package/dist/assets/chunk-V7JOEXUC-BLEuBf1s.js +206 -0
  49. package/dist/assets/chunk-VAUOI2AC-Dpcz6BhT.js +1 -0
  50. package/dist/assets/chunk-VR4S4FIN-BAUbR0ke.js +1 -0
  51. package/dist/assets/chunk-WYO6CB5R-Do6SJrnY.js +127 -0
  52. package/dist/assets/chunk-XXDRQBXY-C2QjjwYX.js +1 -0
  53. package/dist/assets/chunk-Y2CYZVJY-BGrd8tLy.js +1 -0
  54. package/dist/assets/chunk-YOTPTUD7-CQqvMTCr.js +1 -0
  55. package/dist/assets/chunk-ZGVPDNZ5-B7kWWaXv.js +62 -0
  56. package/dist/assets/chunk-ZIRB5QZD-DTU0cDNh.js +32 -0
  57. package/dist/assets/classDiagram-OUVF2IWQ-DESMaY3_.js +1 -0
  58. package/dist/assets/classDiagram-v2-EOCWNBFH-DwOlByYH.js +1 -0
  59. package/dist/assets/{config-split-page-DBgt9S8b.js → config-split-page-CqlqjnCK.js} +1 -1
  60. package/dist/assets/{confirm-dialog-GSXIvoRU.js → confirm-dialog-bAp8NU93.js} +1 -1
  61. package/dist/assets/cose-bilkent-JH36ORCC-B9LrtV-q.js +1 -0
  62. package/dist/assets/cynefin-VYW2F7L2-CzjeeOq9.js +1 -0
  63. package/dist/assets/cynefinDiagram-TSTJHNR4-BfjvW4su.js +62 -0
  64. package/dist/assets/cytoscape.esm-CRW8q92n.js +321 -0
  65. package/dist/assets/dagre-DkiXZKkL.js +1 -0
  66. package/dist/assets/dagre-VKFMJZFB-B1pQKQSY.js +4 -0
  67. package/dist/assets/defaultLocale-Cdv9x6q7.js +1 -0
  68. package/dist/assets/desktop-update-config-2SjMfCMl.js +1 -0
  69. package/dist/assets/diagram-FQU43EPY-cB4EZdaP.js +3 -0
  70. package/dist/assets/diagram-G47NLZAW-CtW8im1B.js +24 -0
  71. package/dist/assets/diagram-NH7WQ7WH-BDCtrCCm.js +24 -0
  72. package/dist/assets/diagram-OA4YK3LP-DEMJ42Gt.js +30 -0
  73. package/dist/assets/diagram-WEI45ONY-CdCcxSYU.js +41 -0
  74. package/dist/assets/{dist-DC7f4TJH.js → dist-CrtbGLlh.js} +1 -1
  75. package/dist/assets/dist-dw-gAfM_.js +1 -0
  76. package/dist/assets/doc-browser-A5IRyDXQ2.js +1 -0
  77. package/dist/assets/{doc-browser-D0monHjG.js → doc-browser-BmnQa30-.js} +1 -1
  78. package/dist/assets/{doc-browser-context-CGwEOhBy.js → doc-browser-context-BlpUUFrR.js} +1 -1
  79. package/dist/assets/{docx-preview-eFH9DRsS.js → docx-preview-zis3U0Fa.js} +1 -1
  80. package/dist/assets/{download-DiP9zx-J.js → download-BhZZNXD8.js} +1 -1
  81. package/dist/assets/ebnfDiagram-CCIWWBDH-DBZETBwE.js +1 -0
  82. package/dist/assets/erDiagram-Q63AITRT-y2zDXprP.js +85 -0
  83. package/dist/assets/eventmodeling-45OFAUF4-C38BqG8w.js +1 -0
  84. package/dist/assets/{external-link-C6ZeSPef.js → external-link-B9Uoq5J7.js} +1 -1
  85. package/dist/assets/{eye-suvKmtwn.js → eye-K5knauEM.js} +1 -1
  86. package/dist/assets/flowDiagram-23GEKE2U-BoKk8RRq.js +1 -0
  87. package/dist/assets/{folder-CMELbQGG.js → folder-BIYZZWJv.js} +1 -1
  88. package/dist/assets/{form-actions-CUAzpNe_.js → form-actions-CKprWFeM.js} +1 -1
  89. package/dist/assets/ganttDiagram-NO4QXBWP-D2DP344e.js +292 -0
  90. package/dist/assets/gitGraph-TEB2WS4Q-IsQHHJuS.js +1 -0
  91. package/dist/assets/gitGraphDiagram-IHSO6WYX-D37uiwA2.js +106 -0
  92. package/dist/assets/graphlib-BcNFQr5y.js +1 -0
  93. package/dist/assets/index-BVn702m4.css +1 -0
  94. package/dist/assets/index-CFxJ1Fkg.js +2 -0
  95. package/dist/assets/info-DKCQHKI2-ofy2qlHI.js +1 -0
  96. package/dist/assets/infoDiagram-FWYZ7A6U-CG-WAIRi.js +2 -0
  97. package/dist/assets/init-D8TjIqfm.js +1 -0
  98. package/dist/assets/ishikawaDiagram-FXEZZL3T-WeCTnW0f.js +70 -0
  99. package/dist/assets/journeyDiagram-5HDEW3XC-DUFBW9Td.js +139 -0
  100. package/dist/assets/kanban-definition-HUTT4EX6-B0lxgMpg.js +89 -0
  101. package/dist/assets/katex-wEmrEFTg.js +257 -0
  102. package/dist/assets/line-B78frP_c.js +1 -0
  103. package/dist/assets/linear-C6lVEcb2.js +1 -0
  104. package/dist/assets/loader-circle-Bj2RMrCR.js +1 -0
  105. package/dist/assets/{logo-badge-Cu5UGH_4.js → logo-badge-BQ-XFZod.js} +1 -1
  106. package/dist/assets/map-Bdri5UL5.js +1 -0
  107. package/dist/assets/mcp-marketplace-page-BfA7A3n4.js +9 -0
  108. package/dist/assets/mcp-marketplace-page-CXHYO95j.js +1 -0
  109. package/dist/assets/mermaid-parser.core-Cue18vLo.js +4 -0
  110. package/dist/assets/mermaid.core-CXwWRh5i.js +11 -0
  111. package/dist/assets/mindmap-definition-LN4V7U3C-CFxz0P0-.js +96 -0
  112. package/dist/assets/{model-config-page-CWP6pyBm.js → model-config-page-BbWbW_qM.js} +1 -1
  113. package/dist/assets/ordinal-Cbd8t4LB.js +1 -0
  114. package/dist/assets/packet-7NZHBO7P-Rfopfa1o.js +1 -0
  115. package/dist/assets/path-DzBnMI1r.js +1 -0
  116. package/dist/assets/pegDiagram-2B236MQR-B_Qt9zyT.js +1 -0
  117. package/dist/assets/pie-RZYD4A2V-CJR1xKEv.js +1 -0
  118. package/dist/assets/pieDiagram-ENE6RG2P-CRQdSuwS.js +39 -0
  119. package/dist/assets/plus-TmFdwO3P.js +1 -0
  120. package/dist/assets/{popover-DImguRok.js → popover-CX9zFsuc.js} +1 -1
  121. package/dist/assets/preload-helper-D4M6sveU.js +1 -0
  122. package/dist/assets/{provider-scoped-model-input-CE7p4SbJ.js → provider-scoped-model-input-B-9_DtIP.js} +1 -1
  123. package/dist/assets/providers-config-page-xwIjGur2.js +1 -0
  124. package/dist/assets/quadrantDiagram-ABIIQ3AL-D5kPpttq.js +7 -0
  125. package/dist/assets/radar-I7S5WNFK-CDg8Z-1r.js +1 -0
  126. package/dist/assets/railroad-3IZDKUUU-BHnNmjxO.js +1 -0
  127. package/dist/assets/railroad-abnf-AHOZXSZD-DGtNcusW.js +1 -0
  128. package/dist/assets/railroad-ebnf-EBAXGLYW-BRFtsQmm.js +1 -0
  129. package/dist/assets/railroad-peg-LSFZ7HO6-Bj5IypCA.js +1 -0
  130. package/dist/assets/railroadDiagram-RFXS5EU6-DGh_TGlQ.js +1 -0
  131. package/dist/assets/{react-5sjAT_6d.js → react-DsgudwpT.js} +1 -1
  132. package/dist/assets/{refresh-cw-CHLBzWWj.js → refresh-cw-kXdvbhFf.js} +1 -1
  133. package/dist/assets/remote-B9F2uCk9.js +1 -0
  134. package/dist/assets/requirementDiagram-TGXJPOKE-EmFph0H_.js +84 -0
  135. package/dist/assets/rotate-cw-DEc6aihr.js +1 -0
  136. package/dist/assets/rough.esm-wJ1d3nQl.js +1 -0
  137. package/dist/assets/runtime-config-page-DTsi7czJ.js +1 -0
  138. package/dist/assets/sankeyDiagram-HTMAVEWB-BXWB5Ugi.js +40 -0
  139. package/dist/assets/{save-BABzyrjB.js → save-C9qpz2_2.js} +1 -1
  140. package/dist/assets/{search-BTqxG3ZG.js → search-_kEi29qo.js} +1 -1
  141. package/dist/assets/{search-config-page-BkKKmxmS.js → search-config-page-H7ug8f4D.js} +1 -1
  142. package/dist/assets/{secrets-config-page-CWJMyu-k.js → secrets-config-page-C1_6rINu.js} +2 -2
  143. package/dist/assets/security-config-Cr2ERKLu.js +1 -0
  144. package/dist/assets/{select-TYZDBNmq.js → select-MJKhKAWR.js} +1 -1
  145. package/dist/assets/sequenceDiagram-DBY2YBRQ-ClKnO7v0.js +162 -0
  146. package/dist/assets/{setting-row-DR23Z_5I.js → setting-row-DA3tJuew.js} +1 -1
  147. package/dist/assets/{settings-2-D8WWcBnw.js → settings-2-D_y7zXJz.js} +1 -1
  148. package/dist/assets/sizeCapture-X5ZJPWSS-DSo-t5ou.js +1 -0
  149. package/dist/assets/{skeleton-CloHeg0S.js → skeleton-0uTLUhm9.js} +1 -1
  150. package/dist/assets/src-Dad2ZbCt.js +1 -0
  151. package/dist/assets/stateDiagram-2N3HPSRC-BvAalBfz.js +1 -0
  152. package/dist/assets/stateDiagram-v2-6OUMAXLB-CfMU38yc.js +1 -0
  153. package/dist/assets/swimlanes-5IMT3BWC-DdVqVa1a.js +2 -0
  154. package/dist/assets/swimlanesDiagram-G3AALYLV-D9Zb9X57.js +8 -0
  155. package/dist/assets/{tabs-CX-ckWoo.js → tabs-h44XPocd.js} +1 -1
  156. package/dist/assets/{tag-chip-mCc23Rhb.js → tag-chip-bRZ04q3C.js} +1 -1
  157. package/dist/assets/timeline-definition-FHXFAJF6-D4ar8IQY.js +120 -0
  158. package/dist/assets/{trash-2-SAW1Z4Po.js → trash-2-BRpSE-Wt.js} +1 -1
  159. package/dist/assets/treeView-QDETBFTQ-Uy7M0WvH.js +1 -0
  160. package/dist/assets/treemap-6X3UGDF4-BJfGV7KX.js +1 -0
  161. package/dist/assets/use-config-CwDEAl6f.js +1 -0
  162. package/dist/assets/{use-confirm-dialog-C95F6sT1.js → use-confirm-dialog-BbnWw-8c.js} +1 -1
  163. package/dist/assets/{use-viewport-layout-C6m3Nte7.js → use-viewport-layout-CqgIMDQL.js} +1 -1
  164. package/dist/assets/vennDiagram-L72KCM5P-CDXJ_i3p.js +34 -0
  165. package/dist/assets/wardley-OPB4EBWU-q263iF3K.js +1 -0
  166. package/dist/assets/wardleyDiagram-EHGQE667-D-dSuT_7.js +78 -0
  167. package/dist/assets/x-DdEDL6ju.js +1 -0
  168. package/dist/assets/xychartDiagram-FW5EYKEG-C9JCzsZF.js +7 -0
  169. package/dist/index.html +33 -32
  170. package/package.json +8 -8
  171. package/src/features/chat/components/chat-popover-content.tsx +1 -26
  172. package/src/features/chat/components/conversation/__tests__/chat-conversation-header-section.test.tsx +84 -1
  173. package/src/features/chat/components/conversation/chat-conversation-header-section.tsx +13 -2
  174. package/src/features/chat/components/conversation/chat-conversation-workspace-section.tsx +1 -0
  175. package/src/features/chat/components/layout/__tests__/chat-sidebar-test.utils.ts +22 -0
  176. package/src/features/chat/components/layout/__tests__/chat-sidebar.test.tsx +13 -24
  177. package/src/features/chat/components/layout/chat-sidebar-desktop-layout.tsx +16 -4
  178. package/src/features/chat/components/layout/chat-sidebar.tsx +38 -2
  179. package/src/features/chat/features/conversation/components/__tests__/session-conversation-area.test.tsx +1 -0
  180. package/src/features/chat/features/conversation/components/__tests__/session-conversation-input.streaming.test.tsx +52 -2
  181. package/src/features/chat/features/conversation/components/session-conversation-area.tsx +22 -0
  182. package/src/features/chat/features/conversation/components/session-conversation-input.tsx +36 -20
  183. package/src/features/chat/features/conversation/hooks/__tests__/use-session-conversation-input-state.test.tsx +55 -0
  184. package/src/features/chat/features/conversation/hooks/use-session-conversation-input-state.ts +12 -11
  185. package/src/features/chat/features/conversation/hooks/use-session-conversation-preference-actions.ts +94 -0
  186. package/src/features/chat/features/input/hooks/__tests__/use-chat-input-surface-state.test.tsx +37 -0
  187. package/src/features/chat/features/input/hooks/use-chat-input-surface-state.ts +140 -63
  188. package/src/features/chat/features/input/input-surface-plugins/__tests__/context-reference-plugin.test.ts +175 -0
  189. package/src/features/chat/features/input/input-surface-plugins/__tests__/slash-command-plugin.test.ts +66 -0
  190. package/src/features/chat/features/input/input-surface-plugins/chat-input-product-plugin-adapters.types.ts +8 -1
  191. package/src/features/chat/features/input/input-surface-plugins/context-reference-plugin.utils.ts +274 -0
  192. package/src/features/chat/features/input/input-surface-plugins/slash-command-plugin.utils.ts +18 -11
  193. package/src/features/chat/features/input/utils/__tests__/chat-composer-state.utils.test.ts +26 -0
  194. package/src/features/chat/features/input/utils/__tests__/chat-inline-token.utils.test.ts +50 -4
  195. package/src/features/chat/features/input/utils/__tests__/chat-input-bar.utils.test.ts +51 -0
  196. package/src/features/chat/features/input/utils/chat-composer-state.utils.ts +4 -10
  197. package/src/features/chat/features/input/utils/chat-composer-token-protocol.utils.ts +29 -0
  198. package/src/features/chat/features/input/utils/chat-inline-token.utils.ts +66 -8
  199. package/src/features/chat/features/input/utils/chat-input-bar.utils.ts +62 -21
  200. package/src/features/chat/features/message/components/__tests__/chat-message-list.container.test.tsx +42 -10
  201. package/src/features/chat/features/message/components/chat-inline-file-preview.tsx +80 -0
  202. package/src/features/chat/features/message/components/chat-message-list.container.tsx +55 -60
  203. package/src/features/chat/features/message/utils/chat-message-texts.utils.ts +2 -0
  204. package/src/features/chat/features/ncp/hooks/__tests__/use-hydrated-ncp-agent.test.tsx +41 -3
  205. package/src/features/chat/features/project/components/__tests__/chat-project-create-dialog.test.tsx +67 -0
  206. package/src/features/chat/features/project/components/chat-project-create-dialog.tsx +203 -0
  207. package/src/features/chat/features/session/components/session-header/__tests__/chat-session-header-actions.test.tsx +7 -7
  208. package/src/features/chat/features/session/components/session-header/__tests__/chat-session-project-dialog.test.tsx +47 -0
  209. package/src/features/chat/features/session/components/session-header/chat-session-header-actions.tsx +6 -3
  210. package/src/features/chat/features/session/components/session-header/chat-session-project-badge.tsx +3 -0
  211. package/src/features/chat/features/session/components/session-header/chat-session-project-dialog.tsx +3 -0
  212. package/src/features/chat/features/session/components/session-header/chat-session-switcher-groups.tsx +245 -0
  213. package/src/features/chat/features/session/components/session-header/chat-session-title-switcher.tsx +81 -127
  214. package/src/features/chat/features/session/utils/__tests__/chat-sidebar-session-groups.utils.test.ts +22 -0
  215. package/src/features/chat/features/session/utils/chat-run-metadata.utils.ts +6 -6
  216. package/src/features/chat/features/session/utils/chat-sidebar-session-groups.utils.ts +12 -1
  217. package/src/features/chat/features/welcome/components/chat-conversation-welcome.tsx +3 -0
  218. package/src/features/chat/features/welcome/components/chat-welcome.tsx +1 -0
  219. package/src/features/chat/features/welcome/utils/__tests__/chat-welcome-project-options.utils.test.ts +20 -0
  220. package/src/features/chat/features/welcome/utils/chat-welcome-project-options.utils.ts +15 -2
  221. package/src/features/chat/features/workspace/components/__tests__/chat-session-workspace-file-preview.test.tsx +22 -24
  222. package/src/features/chat/features/workspace/components/__tests__/chat-session-workspace-panel.test.tsx +2 -1
  223. package/src/features/chat/features/workspace/components/chat-session-workspace-file-breadcrumbs.tsx +6 -17
  224. package/src/features/chat/features/workspace/components/chat-session-workspace-file-preview.tsx +41 -19
  225. package/src/features/chat/features/workspace/components/chat-session-workspace-panel-nav.tsx +5 -1
  226. package/src/features/chat/features/workspace/components/chat-session-workspace-panel.tsx +5 -1
  227. package/src/features/chat/features/workspace/utils/__tests__/chat-workspace-panel-view-model.utils.test.ts +104 -3
  228. package/src/features/chat/features/workspace/utils/chat-thread-workspace-session.utils.ts +83 -0
  229. package/src/features/chat/features/workspace/utils/chat-workspace-file-tab.utils.ts +4 -1
  230. package/src/features/chat/features/workspace/utils/chat-workspace-panel-view-model.utils.ts +96 -25
  231. package/src/features/chat/managers/__tests__/chat-thread-workspace.manager.test.ts +25 -0
  232. package/src/features/chat/managers/__tests__/chat-thread.manager.test.ts +5 -1
  233. package/src/features/chat/managers/chat-session-list.manager.ts +1 -0
  234. package/src/features/chat/managers/chat-thread.manager.ts +15 -37
  235. package/src/features/chat/pages/__tests__/ncp-chat-page.test.ts +23 -0
  236. package/src/features/chat/stores/__tests__/chat-thread.store.test.ts +9 -0
  237. package/src/features/chat/stores/chat-thread.store.ts +16 -36
  238. package/src/features/chat/types/chat-input-bar.types.ts +2 -0
  239. package/src/features/settings/components/config/provider-form.tsx +0 -4
  240. package/src/features/settings/utils/__tests__/provider-form-support.utils.test.ts +7 -9
  241. package/src/features/settings/utils/provider-form-model.utils.ts +0 -4
  242. package/src/features/system-status/hooks/use-current-version-release-notes-link.ts +33 -0
  243. package/src/features/system-status/index.ts +9 -0
  244. package/src/features/system-status/utils/update-release-notes.utils.ts +44 -3
  245. package/src/index.css +10 -16
  246. package/src/shared/components/common/__tests__/brand-header.test.tsx +127 -10
  247. package/src/shared/components/common/brand-header.tsx +142 -56
  248. package/src/shared/components/path-picker/__tests__/server-path-picker-dialog.test.tsx +288 -38
  249. package/src/shared/components/path-picker/server-path-picker-dialog.tsx +290 -199
  250. package/src/shared/components/path-picker/server-path-picker-directory-list.tsx +103 -0
  251. package/src/shared/components/path-picker/server-path-picker-footer.tsx +58 -0
  252. package/src/shared/components/path-picker/server-path-picker-locations.tsx +148 -0
  253. package/src/shared/components/path-picker/server-path-picker-navigation-bar.tsx +198 -0
  254. package/src/shared/components/path-picker/server-path-picker-new-folder.tsx +59 -0
  255. package/src/shared/components/path-picker/server-path-picker-toolbar.tsx +25 -0
  256. package/src/shared/hooks/use-projects.ts +28 -0
  257. package/src/shared/hooks/use-server-path-create-directory.ts +6 -0
  258. package/src/shared/hooks/use-server-path-read.ts +10 -5
  259. package/src/shared/hooks/use-server-path-search.ts +40 -0
  260. package/src/shared/lib/api/index.ts +1 -0
  261. package/src/shared/lib/api/projects/project.utils.ts +14 -0
  262. package/src/shared/lib/api/types.ts +30 -3
  263. package/src/shared/lib/api/utils/server-path.utils.ts +22 -1
  264. package/src/shared/lib/i18n/locales/en-US/chat.json +43 -2
  265. package/src/shared/lib/i18n/locales/en-US/core.json +2 -3
  266. package/src/shared/lib/i18n/locales/en-US/desktop-update.json +4 -0
  267. package/src/shared/lib/i18n/locales/en-US/path-picker.json +25 -0
  268. package/src/shared/lib/i18n/locales/zh-CN/chat.json +43 -2
  269. package/src/shared/lib/i18n/locales/zh-CN/core.json +2 -3
  270. package/src/shared/lib/i18n/locales/zh-CN/desktop-update.json +4 -0
  271. package/src/shared/lib/i18n/locales/zh-CN/path-picker.json +25 -0
  272. package/src/shared/lib/session-project/__tests__/workspace-file-breadcrumb.test.ts +1 -4
  273. package/src/shared/lib/session-project/workspace-file-breadcrumb.utils.ts +1 -18
  274. package/dist/assets/api-BOXOrpcJ.js +0 -13
  275. package/dist/assets/app-presenter-provider-CkHLbvZQ.js +0 -106
  276. package/dist/assets/desktop-update-config-7bmNeWIC.js +0 -1
  277. package/dist/assets/doc-browser-CgdoLUT92.js +0 -1
  278. package/dist/assets/index-BFeNDQ4R.css +0 -1
  279. package/dist/assets/index-D_6U-M94.js +0 -2
  280. package/dist/assets/loader-circle-tYHStefV.js +0 -1
  281. package/dist/assets/mcp-marketplace-page-BUzHDwW6.js +0 -9
  282. package/dist/assets/mcp-marketplace-page-ohUAdfqJ.js +0 -1
  283. package/dist/assets/plus-tywizkeY.js +0 -1
  284. package/dist/assets/providers-config-page-BKPo8SjH.js +0 -1
  285. package/dist/assets/remote-DXJryUxB.js +0 -1
  286. package/dist/assets/rotate-cw-BefO5brU.js +0 -1
  287. package/dist/assets/runtime-config-page-1Axv4QGu.js +0 -1
  288. package/dist/assets/security-config-DeQUIXfi.js +0 -1
  289. package/dist/assets/use-config-BucdaK23.js +0 -1
  290. package/dist/assets/x-DtCPHwCu.js +0 -1
  291. package/src/features/chat/components/__tests__/chat-popover-content.test.tsx +0 -41
  292. package/src/features/chat/components/layout/__tests__/chat-sidebar-utility-menu.test.tsx +0 -55
  293. package/src/features/chat/features/input/input-surface-plugins/__tests__/panel-app-reference-plugin.test.ts +0 -77
  294. package/src/features/chat/features/input/input-surface-plugins/panel-app-reference-plugin.utils.ts +0 -144
  295. /package/dist/assets/{config-hints-fDrYfl0l.js → config-hints-LxG80Uj5.js} +0 -0
  296. /package/dist/assets/{host-capabilities-zZvY9U63.js → host-capabilities-CFbAfrrA.js} +0 -0
  297. /package/dist/assets/{jszip.min-Das2IAnb.js → jszip.min-CP5uPNQa.js} +0 -0
  298. /package/dist/assets/{middleware-BLMHv9YF.js → middleware-CAXxl-af.js} +0 -0
  299. /package/dist/assets/{provider-models-C_yOh6DE.js → provider-models-BoUhFo9R.js} +0 -0
  300. /package/dist/assets/{xlsx-B6zVt1RK.js → xlsx-DUqJhg5u.js} +0 -0
@@ -16,6 +16,7 @@ import { useChatQueryStore } from "@/features/chat/stores/ncp-chat-query.store";
16
16
  import { useChatSessionListStore } from "@/features/chat/stores/chat-session-list.store";
17
17
  import { useChatThreadStore } from "@/features/chat/stores/chat-thread.store";
18
18
  import { useAgents } from "@/shared/hooks/use-agents";
19
+ import { useProjects } from "@/shared/hooks/use-projects";
19
20
  import type { NcpSessionSummaryView } from "@/shared/lib/api";
20
21
  import { normalizeSessionProjectRootValue } from "@/shared/lib/session-project";
21
22
 
@@ -55,6 +56,7 @@ export function ChatConversationWelcome({
55
56
  (state) => state.snapshot.sessionTypesQuery?.data ?? null,
56
57
  );
57
58
  const agentsQuery = useAgents();
59
+ const projectsQuery = useProjects();
58
60
  const draftAgentId = threadSnapshot.agentId ?? selectedAgentId;
59
61
  const availableAgents = resolveChatWelcomeAgents({
60
62
  agents: agentsQuery.data?.agents,
@@ -80,6 +82,7 @@ export function ChatConversationWelcome({
80
82
  });
81
83
  const projectOptions = buildChatWelcomeProjectOptions({
82
84
  defaultProjectRoot,
85
+ projects: projectsQuery.data?.projects ?? [],
83
86
  sessionSummaries,
84
87
  });
85
88
  const selectDraftAgent = (agentId: string) => {
@@ -106,6 +106,7 @@ export function ChatWelcome({
106
106
  <ChatSessionProjectDialog
107
107
  open={isProjectDialogOpen}
108
108
  currentProjectRoot={resolvedProjectRoot}
109
+ defaultWorkspacePath={defaultProjectRoot}
109
110
  isSaving={isProjectSaving}
110
111
  onOpenChange={setIsProjectDialogOpen}
111
112
  onSave={saveProjectRoot}
@@ -48,4 +48,24 @@ describe("buildChatWelcomeProjectOptions", () => {
48
48
  },
49
49
  ]);
50
50
  });
51
+
52
+ it("includes a registered project before it has any sessions", () => {
53
+ const options = buildChatWelcomeProjectOptions({
54
+ defaultProjectRoot: "/Users/demo/.nextclaw/workspace",
55
+ projects: [{
56
+ name: "Knowledge",
57
+ rootPath: "/tmp/knowledge",
58
+ template: "knowledge-base",
59
+ createdAt: "2026-07-15T00:00:00.000Z",
60
+ updatedAt: "2026-07-15T00:00:00.000Z",
61
+ }],
62
+ sessionSummaries: [],
63
+ });
64
+
65
+ expect(options).toEqual([{
66
+ projectRoot: "/tmp/knowledge",
67
+ projectName: "Knowledge",
68
+ sessionCount: 0,
69
+ }]);
70
+ });
51
71
  });
@@ -1,4 +1,4 @@
1
- import type { NcpSessionSummaryView } from "@/shared/lib/api";
1
+ import type { NcpSessionSummaryView, ProjectView } from "@/shared/lib/api";
2
2
  import { adaptNcpSessionSummaries } from "@/features/chat/features/session/utils/ncp-session-adapter.utils";
3
3
  import { getSessionProjectName } from "@/shared/lib/session-project";
4
4
 
@@ -17,14 +17,27 @@ function readSessionActivityAt(session: {
17
17
 
18
18
  export function buildChatWelcomeProjectOptions(params: {
19
19
  defaultProjectRoot: string | null;
20
+ projects?: readonly ProjectView[];
20
21
  sessionSummaries: readonly NcpSessionSummaryView[];
21
22
  }): ChatWelcomeProjectOption[] {
22
- const { defaultProjectRoot, sessionSummaries } = params;
23
+ const { defaultProjectRoot, projects = [], sessionSummaries } = params;
23
24
  const groups = new Map<
24
25
  string,
25
26
  ChatWelcomeProjectOption & { latestUpdatedAt: number }
26
27
  >();
27
28
 
29
+ for (const project of projects) {
30
+ if (project.rootPath === defaultProjectRoot) {
31
+ continue;
32
+ }
33
+ groups.set(project.rootPath, {
34
+ projectRoot: project.rootPath,
35
+ projectName: project.name,
36
+ sessionCount: 0,
37
+ latestUpdatedAt: new Date(project.updatedAt).getTime(),
38
+ });
39
+ }
40
+
28
41
  for (const session of adaptNcpSessionSummaries([...sessionSummaries])) {
29
42
  const projectRoot = session.projectRoot?.trim();
30
43
  if (!projectRoot || projectRoot === defaultProjectRoot) {
@@ -6,6 +6,7 @@ import type {
6
6
  } from "@nextclaw/agent-chat-ui";
7
7
  import { ChatSessionWorkspaceFilePreview } from "@/features/chat/features/workspace/components/chat-session-workspace-file-preview";
8
8
  import type { ChatWorkspaceFileTab } from "@/features/chat/stores/chat-thread.store";
9
+ import type { ServerPathReadView } from "@/shared/lib/api";
9
10
  import { t } from "@/shared/lib/i18n";
10
11
 
11
12
  const serverPathReadMock = vi.fn();
@@ -22,15 +23,10 @@ type RenderWorkspaceFilePreviewOptions = {
22
23
  refreshVersion?: number;
23
24
  sessionProjectRoot?: string | null;
24
25
  sessionWorkingDir?: string | null;
26
+ showBreadcrumbs?: boolean;
25
27
  };
26
28
 
27
- type TextReadDataOverrides = {
28
- kind?: "text" | "markdown" | "binary";
29
- languageHint?: string;
30
- resolvedPath?: string;
31
- text?: string;
32
- truncated?: boolean;
33
- };
29
+ type TextReadDataOverrides = Partial<ServerPathReadView>;
34
30
 
35
31
  vi.mock("@/shared/hooks/use-server-path-read", () => ({
36
32
  useServerPathRead: (...args: unknown[]) => serverPathReadMock(...args),
@@ -97,6 +93,7 @@ function mockTextRead(overrides: TextReadDataOverrides = {}) {
97
93
  data: {
98
94
  kind: "text",
99
95
  resolvedPath: "/tmp/example.ts",
96
+ startLine: 1,
100
97
  text: "const answer = 42;\n",
101
98
  truncated: false,
102
99
  ...overrides,
@@ -110,6 +107,7 @@ function renderWorkspaceFilePreview({
110
107
  refreshVersion,
111
108
  sessionProjectRoot = "/tmp",
112
109
  sessionWorkingDir = "/tmp",
110
+ showBreadcrumbs,
113
111
  }: RenderWorkspaceFilePreviewOptions = {}) {
114
112
  const view = render(
115
113
  <ChatSessionWorkspaceFilePreview
@@ -117,6 +115,7 @@ function renderWorkspaceFilePreview({
117
115
  sessionProjectRoot={sessionProjectRoot}
118
116
  sessionWorkingDir={sessionWorkingDir}
119
117
  refreshVersion={refreshVersion}
118
+ showBreadcrumbs={showBreadcrumbs}
120
119
  onFileOpen={onFileOpen}
121
120
  />,
122
121
  );
@@ -525,11 +524,16 @@ describe("ChatSessionWorkspaceFilePreview text rendering", () => {
525
524
  ).toBe("workspace");
526
525
  });
527
526
 
528
- it("passes server language hints to the workspace code surface", () => {
529
- mockTextRead({ languageHint: "js", resolvedPath: "/tmp/example.js" });
527
+ it("passes server language hints and keeps location out of breadcrumbs", () => {
528
+ mockTextRead({
529
+ languageHint: "js",
530
+ resolvedPath: "/tmp/example.js",
531
+ });
530
532
  renderWorkspaceFilePreview({
531
533
  file: {
534
+ column: 4,
532
535
  label: "example.js",
536
+ line: 12,
533
537
  path: "/tmp/example.js",
534
538
  viewMode: "preview",
535
539
  },
@@ -540,6 +544,7 @@ describe("ChatSessionWorkspaceFilePreview text rendering", () => {
540
544
  .getByTestId("file-code-surface")
541
545
  .getAttribute("data-language-hint"),
542
546
  ).toBe("js");
547
+ expect(screen.queryByText("L12:4")).toBeNull();
543
548
  });
544
549
 
545
550
  it("renders Markdown by default and keeps source view explicit", () => {
@@ -761,6 +766,14 @@ describe("ChatSessionWorkspaceFilePreview text rendering", () => {
761
766
  });
762
767
 
763
768
  describe("ChatSessionWorkspaceFilePreview breadcrumbs", () => {
769
+ it("can hide workspace breadcrumbs for inline placements", () => {
770
+ mockTextRead();
771
+ renderWorkspaceFilePreview({ showBreadcrumbs: false });
772
+
773
+ expect(screen.queryByTestId("workspace-file-breadcrumbs")).toBeNull();
774
+ expect(screen.getByTestId("file-code-surface")).toBeTruthy();
775
+ });
776
+
764
777
  it("renders project-relative breadcrumbs when the file is inside the workspace", () => {
765
778
  mockTextRead({ resolvedPath: "/tmp/workspace/src/example.ts" });
766
779
  renderWorkspaceFilePreview({
@@ -832,21 +845,6 @@ describe("ChatSessionWorkspaceFilePreview breadcrumbs", () => {
832
845
  ).toContain("h-6");
833
846
  });
834
847
 
835
- it("keeps line and truncation metadata without the duplicated type badge", () => {
836
- mockTextRead({ truncated: true });
837
- renderWorkspaceFilePreview({
838
- file: {
839
- viewMode: "preview",
840
- line: 12,
841
- column: 4,
842
- },
843
- });
844
-
845
- expect(screen.getByText("L12:4")).toBeTruthy();
846
- expect(screen.getByText(t("chatWorkspacePreviewTruncated"))).toBeTruthy();
847
- expect(screen.queryByText(t("chatWorkspacePreview"))).toBeNull();
848
- });
849
-
850
848
  it("uses the session working directory as the base path when no project root is set", () => {
851
849
  mockTextRead({
852
850
  resolvedPath: "/tmp/agent-workspace/AGENTS.md",
@@ -9,7 +9,7 @@ const mocks = vi.hoisted(() => ({
9
9
  syncVisibleWorkspaceSelection: vi.fn(),
10
10
  selectChildSessionDetail: vi.fn(),
11
11
  selectWorkspaceFile: vi.fn(),
12
- closeWorkspaceFile: vi.fn(),
12
+ closeWorkspaceTab: vi.fn(),
13
13
  openSessionCronPanel: vi.fn(),
14
14
  goBackWorkspacePanel: vi.fn(),
15
15
  goForwardWorkspacePanel: vi.fn(),
@@ -72,6 +72,7 @@ function renderPanel(displayMode: "docked" | "overlay" = "docked") {
72
72
  activeSideChatDraft={null}
73
73
  workspaceFileTabs={[fileTab]}
74
74
  activeWorkspaceFileKey={fileTab.key}
75
+ closedWorkspaceTabEntries={[]}
75
76
  workspaceNavigationHistory={[{ kind: "file", key: fileTab.key }]}
76
77
  workspaceNavigationHistoryIndex={0}
77
78
  activePanelKind="file"
@@ -12,15 +12,10 @@ import type {
12
12
  import { t } from "@/shared/lib/i18n";
13
13
  import { cn } from "@/shared/lib/utils";
14
14
 
15
- function WorkspaceBreadcrumbMetaChip({ tone = "neutral", value }: { tone?: "neutral" | "warning"; value: string }) {
15
+ function WorkspaceBreadcrumbWarningChip({ value }: { value: string }) {
16
16
  return (
17
17
  <span
18
- className={cn(
19
- "inline-flex h-5 items-center rounded-sm border px-1.5 text-[10px] font-medium leading-none",
20
- tone === "warning"
21
- ? "border-amber-200 bg-amber-50 text-amber-700"
22
- : "border-gray-200 bg-gray-50 text-gray-500",
23
- )}
18
+ className="inline-flex h-5 items-center rounded-sm border border-amber-200 bg-amber-50 px-1.5 text-[10px] font-medium leading-none text-amber-700"
24
19
  >
25
20
  {value}
26
21
  </span>
@@ -116,17 +111,11 @@ export function ChatSessionWorkspaceFileBreadcrumbs({
116
111
  ))}
117
112
  </div>
118
113
 
119
- {breadcrumb.locationLabel || breadcrumb.truncated ? (
114
+ {breadcrumb.truncated ? (
120
115
  <div className="flex shrink-0 flex-wrap items-center justify-end gap-1">
121
- {breadcrumb.locationLabel ? (
122
- <WorkspaceBreadcrumbMetaChip value={breadcrumb.locationLabel} />
123
- ) : null}
124
- {breadcrumb.truncated ? (
125
- <WorkspaceBreadcrumbMetaChip
126
- tone="warning"
127
- value={t("chatWorkspacePreviewTruncated")}
128
- />
129
- ) : null}
116
+ <WorkspaceBreadcrumbWarningChip
117
+ value={t("chatWorkspacePreviewTruncated")}
118
+ />
130
119
  </div>
131
120
  ) : null}
132
121
  </div>
@@ -51,10 +51,10 @@ function buildPreviewBlock(params: {
51
51
  path: string;
52
52
  text: string;
53
53
  languageHint?: string | null;
54
- line?: number | null;
54
+ startLine?: number | null;
55
55
  }): ChatFileOperationBlockViewModel {
56
- const { languageHint, line, path, text } = params;
57
- const startLine = line ?? 1;
56
+ const { languageHint, path, startLine, text } = params;
57
+ const normalizedStartLine = startLine ?? 1;
58
58
  return {
59
59
  key: `preview:${path}`,
60
60
  path,
@@ -62,13 +62,13 @@ function buildPreviewBlock(params: {
62
62
  lines: buildPreviewLines({
63
63
  text,
64
64
  kind: "context",
65
- oldStartLine: startLine,
66
- newStartLine: startLine,
65
+ oldStartLine: normalizedStartLine,
66
+ newStartLine: normalizedStartLine,
67
67
  }),
68
68
  rawText: text,
69
69
  languageHint: languageHint ?? null,
70
- oldStartLine: startLine,
71
- newStartLine: startLine,
70
+ oldStartLine: normalizedStartLine,
71
+ newStartLine: normalizedStartLine,
72
72
  };
73
73
  }
74
74
 
@@ -154,12 +154,21 @@ function WorkspaceDiffBody({
154
154
 
155
155
  function WorkspaceCodeSurface({
156
156
  block,
157
+ targetColumn,
158
+ targetLine,
157
159
  }: {
158
160
  block: ChatFileOperationBlockViewModel;
161
+ targetColumn?: number | null;
162
+ targetLine?: number | null;
159
163
  }) {
160
164
  return (
161
165
  <div className="h-full overflow-auto custom-scrollbar bg-white">
162
- <FileOperationCodeSurface block={block} layout="workspace" />
166
+ <FileOperationCodeSurface
167
+ block={block}
168
+ layout="workspace"
169
+ targetColumn={targetColumn}
170
+ targetLine={targetLine}
171
+ />
163
172
  </div>
164
173
  );
165
174
  }
@@ -176,6 +185,8 @@ function WorkspacePreviewBody({
176
185
  previewViewer,
177
186
  previewQuery,
178
187
  previewText,
188
+ targetColumn,
189
+ targetLine,
179
190
  }: {
180
191
  contentUrl: string | null;
181
192
  contentUrlKind: WorkspaceFileContentKind | null;
@@ -188,6 +199,8 @@ function WorkspacePreviewBody({
188
199
  previewViewer: "source" | "rendered" | null;
189
200
  previewQuery: ReturnType<typeof useServerPathRead> | null | undefined;
190
201
  previewText: string | null;
202
+ targetColumn?: number | null;
203
+ targetLine?: number | null;
191
204
  }) {
192
205
  if (contentUrl && contentUrlKind) {
193
206
  return (
@@ -260,7 +273,13 @@ function WorkspacePreviewBody({
260
273
  }
261
274
 
262
275
  if (previewBlock) {
263
- return <WorkspaceCodeSurface block={previewBlock} />;
276
+ return (
277
+ <WorkspaceCodeSurface
278
+ block={previewBlock}
279
+ targetColumn={targetColumn}
280
+ targetLine={targetLine}
281
+ />
282
+ );
264
283
  }
265
284
 
266
285
  return <WorkspaceFilePreviewStatus text={t("chatWorkspacePreviewEmpty")} />;
@@ -271,6 +290,7 @@ type ChatSessionWorkspaceFilePreviewProps = {
271
290
  refreshVersion?: number;
272
291
  sessionProjectRoot: string | null;
273
292
  sessionWorkingDir: string | null;
293
+ showBreadcrumbs?: boolean;
274
294
  onFileOpen: (action: ChatFileOpenActionViewModel) => void;
275
295
  };
276
296
 
@@ -279,6 +299,7 @@ export function ChatSessionWorkspaceFilePreview({
279
299
  refreshVersion = 0,
280
300
  sessionProjectRoot,
281
301
  sessionWorkingDir,
302
+ showBreadcrumbs = true,
282
303
  onFileOpen,
283
304
  }: ChatSessionWorkspaceFilePreviewProps) {
284
305
  const isPreviewMode = file.viewMode === "preview";
@@ -287,6 +308,7 @@ export function ChatSessionWorkspaceFilePreview({
287
308
  const previewQuery = useServerPathRead({
288
309
  path: file.path,
289
310
  basePath: sessionWorkingDir,
311
+ line: file.line,
290
312
  enabled: usesServerPath,
291
313
  });
292
314
  const directoryQuery = useServerPathBrowse({
@@ -351,15 +373,15 @@ export function ChatSessionWorkspaceFilePreview({
351
373
  path: previewQuery?.data?.resolvedPath ?? file.path,
352
374
  text: previewText,
353
375
  languageHint: previewQuery?.data?.languageHint ?? null,
354
- line: file.line,
376
+ startLine: previewQuery?.data?.startLine ?? 1,
355
377
  });
356
378
  }, [
357
379
  contentUrl,
358
- file.line,
359
380
  file.path,
360
381
  isPreviewMode,
361
382
  previewQuery?.data?.languageHint,
362
383
  previewQuery?.data?.resolvedPath,
384
+ previewQuery?.data?.startLine,
363
385
  previewText,
364
386
  ]);
365
387
  const isTextPreviewTruncated = !contentUrl && Boolean(previewQuery?.data?.truncated);
@@ -370,14 +392,10 @@ export function ChatSessionWorkspaceFilePreview({
370
392
  path: resolvedPath,
371
393
  kind: previewPathKind,
372
394
  sessionProjectRoot: breadcrumbBasePath,
373
- line: file.line,
374
- column: file.column,
375
395
  truncated: isTextPreviewTruncated,
376
396
  }),
377
397
  [
378
398
  breadcrumbBasePath,
379
- file.column,
380
- file.line,
381
399
  isTextPreviewTruncated,
382
400
  previewPathKind,
383
401
  resolvedPath,
@@ -386,10 +404,12 @@ export function ChatSessionWorkspaceFilePreview({
386
404
 
387
405
  return (
388
406
  <div className="flex h-full min-h-0 flex-col bg-white">
389
- <ChatSessionWorkspaceFileBreadcrumbs
390
- breadcrumb={breadcrumb}
391
- onFileOpen={onFileOpen}
392
- />
407
+ {showBreadcrumbs ? (
408
+ <ChatSessionWorkspaceFileBreadcrumbs
409
+ breadcrumb={breadcrumb}
410
+ onFileOpen={onFileOpen}
411
+ />
412
+ ) : null}
393
413
 
394
414
  <div className="flex-1 min-h-0 overflow-hidden">
395
415
  {file.viewMode === "diff" ? (
@@ -407,6 +427,8 @@ export function ChatSessionWorkspaceFilePreview({
407
427
  previewViewer={previewViewer}
408
428
  previewQuery={previewQuery}
409
429
  previewText={previewText}
430
+ targetColumn={file.column}
431
+ targetLine={file.line}
410
432
  />
411
433
  )}
412
434
  </div>
@@ -84,7 +84,11 @@ function buildCompactWorkspaceTabs(
84
84
  </span>
85
85
  ) : null,
86
86
  unreadIndicator: tab.showUnreadDot ? <span aria-label={t("chatSessionUnread")} className="h-2 w-2 shrink-0 rounded-full bg-primary" /> : null,
87
- closeLabel: t("chatWorkspaceCloseFile"),
87
+ closeLabel: `${
88
+ tab.kind === "file"
89
+ ? t("chatWorkspaceCloseFile")
90
+ : t("chatWorkspaceCloseTab")
91
+ }: ${tab.title}`,
88
92
  closePlacement: "leading-hover",
89
93
  onSelect: tab.onSelect,
90
94
  onClose: tab.onClose,
@@ -39,6 +39,7 @@ type ChatSessionWorkspacePanelProps = {
39
39
  activeSideChatDraft: ChatWorkspaceSideChatDraft | null;
40
40
  workspaceFileTabs: readonly ChatWorkspaceFileTab[];
41
41
  activeWorkspaceFileKey: string | null;
42
+ closedWorkspaceTabEntries: readonly ChatWorkspaceNavigationEntry[];
42
43
  workspaceNavigationHistory?: readonly ChatWorkspaceNavigationEntry[];
43
44
  workspaceNavigationHistoryIndex?: number;
44
45
  activePanelKind?: ChatWorkspacePanelKind | null;
@@ -56,6 +57,7 @@ export function ChatSessionWorkspacePanel({
56
57
  activeSideChatDraft,
57
58
  workspaceFileTabs,
58
59
  activeWorkspaceFileKey,
60
+ closedWorkspaceTabEntries,
59
61
  workspaceNavigationHistory = [],
60
62
  workspaceNavigationHistoryIndex = 0,
61
63
  activePanelKind,
@@ -95,13 +97,14 @@ export function ChatSessionWorkspacePanel({
95
97
  buildWorkspaceTabsViewModel({
96
98
  resolvedChildTabs,
97
99
  activeSideChatDraft,
100
+ closedWorkspaceTabEntries,
98
101
  workspaceFileTabs,
99
102
  activeSelection,
100
103
  optimisticReadAtBySessionKey,
101
104
  onSelectSession: presenter.chatThreadManager.selectChildSessionDetail,
102
105
  onSelectFile: presenter.chatThreadManager.selectWorkspaceFile,
103
106
  onOpenFileViewer: presenter.chatThreadManager.openWorkspaceFileViewer,
104
- onCloseFile: presenter.chatThreadManager.closeWorkspaceFile,
107
+ onCloseTab: presenter.chatThreadManager.closeWorkspaceTab,
105
108
  onSelectOverview: () => {
106
109
  if (sessionKey)
107
110
  presenter.chatThreadManager.openWorkspaceOverview(sessionKey);
@@ -122,6 +125,7 @@ export function ChatSessionWorkspacePanel({
122
125
  [
123
126
  activeSelection,
124
127
  activeSideChatDraft,
128
+ closedWorkspaceTabEntries,
125
129
  optimisticReadAtBySessionKey,
126
130
  presenter.chatThreadManager,
127
131
  resolvedChildTabs,
@@ -4,6 +4,7 @@ import {
4
4
  buildWorkspaceTabsViewModel,
5
5
  resolveWorkspaceSelection,
6
6
  } from "@/features/chat/features/workspace/utils/chat-workspace-panel-view-model.utils";
7
+ import { createWorkspaceFileTab } from "@/features/chat/features/workspace/utils/chat-workspace-file-tab.utils";
7
8
  import { t } from "@/shared/lib/i18n";
8
9
 
9
10
  function createChildTab(
@@ -104,7 +105,104 @@ describe("resolveWorkspaceSelection", () => {
104
105
  });
105
106
  });
106
107
 
108
+ describe("createWorkspaceFileTab", () => {
109
+ it("uses source view for located Markdown unless rendered view is explicit", () => {
110
+ const locatedSource = createWorkspaceFileTab(
111
+ {
112
+ path: "README.md",
113
+ viewMode: "preview",
114
+ line: 12,
115
+ },
116
+ "parent-1",
117
+ );
118
+ const explicitRendered = createWorkspaceFileTab(
119
+ {
120
+ path: "README.md",
121
+ viewMode: "preview",
122
+ previewViewer: "rendered",
123
+ line: 12,
124
+ },
125
+ "parent-1",
126
+ );
127
+
128
+ expect(locatedSource).toMatchObject({
129
+ previewViewer: "source",
130
+ line: 12,
131
+ });
132
+ expect(explicitRendered).toMatchObject({
133
+ previewViewer: "rendered",
134
+ line: 12,
135
+ });
136
+ });
137
+ });
138
+
107
139
  describe("buildWorkspaceTabsViewModel", () => {
140
+ it("keeps overview fixed while every other visible workspace tab is closable", () => {
141
+ const childTab = createChildTab();
142
+ const draft = {
143
+ draftKey: "draft-1",
144
+ parentSessionKey: "parent-1",
145
+ };
146
+ const fileTab = {
147
+ key: "file-1",
148
+ parentSessionKey: "parent-1",
149
+ path: "README.md",
150
+ viewMode: "preview" as const,
151
+ };
152
+ const onCloseTab = vi.fn();
153
+ const tabs = buildWorkspaceTabsViewModel({
154
+ resolvedChildTabs: [childTab],
155
+ activeSideChatDraft: draft,
156
+ closedWorkspaceTabEntries: [],
157
+ workspaceFileTabs: [fileTab],
158
+ activeSelection: { kind: "overview" },
159
+ optimisticReadAtBySessionKey: {},
160
+ onSelectSession: vi.fn(),
161
+ onSelectFile: vi.fn(),
162
+ onOpenFileViewer: vi.fn(),
163
+ onCloseTab,
164
+ onSelectOverview: vi.fn(),
165
+ onSelectChildSessions: vi.fn(),
166
+ onSelectProjectFiles: vi.fn(),
167
+ onSelectCronJobs: vi.fn(),
168
+ });
169
+
170
+ expect(tabs.find((tab) => tab.kind === "overview")?.onClose).toBeUndefined();
171
+ expect(
172
+ tabs.filter((tab) => tab.kind !== "overview").every((tab) => tab.onClose),
173
+ ).toBe(true);
174
+ tabs.find((tab) => tab.kind === "cron")?.onClose?.();
175
+ expect(onCloseTab).toHaveBeenCalledWith({ kind: "cron" });
176
+ });
177
+
178
+ it("does not project workspace tabs that the user closed", () => {
179
+ const tabs = buildWorkspaceTabsViewModel({
180
+ resolvedChildTabs: [createChildTab()],
181
+ activeSideChatDraft: null,
182
+ closedWorkspaceTabEntries: [
183
+ { kind: "project-files" },
184
+ { kind: "child-session", key: "child-1" },
185
+ ],
186
+ workspaceFileTabs: [],
187
+ activeSelection: { kind: "overview" },
188
+ optimisticReadAtBySessionKey: {},
189
+ onSelectSession: vi.fn(),
190
+ onSelectFile: vi.fn(),
191
+ onOpenFileViewer: vi.fn(),
192
+ onCloseTab: vi.fn(),
193
+ onSelectOverview: vi.fn(),
194
+ onSelectChildSessions: vi.fn(),
195
+ onSelectProjectFiles: vi.fn(),
196
+ onSelectCronJobs: vi.fn(),
197
+ });
198
+
199
+ expect(tabs.map((tab) => tab.key)).toEqual([
200
+ "overview",
201
+ "child-sessions",
202
+ "cron:session",
203
+ ]);
204
+ });
205
+
108
206
  it("builds active and unread state from resolved tabs", () => {
109
207
  const childTab = createChildTab({
110
208
  lastMessageAt: "2026-06-09T10:00:00.000Z",
@@ -115,13 +213,14 @@ describe("buildWorkspaceTabsViewModel", () => {
115
213
  const tabs = buildWorkspaceTabsViewModel({
116
214
  resolvedChildTabs: [childTab],
117
215
  activeSideChatDraft: null,
216
+ closedWorkspaceTabEntries: [],
118
217
  workspaceFileTabs: [],
119
218
  activeSelection: null,
120
219
  optimisticReadAtBySessionKey: {},
121
220
  onSelectSession,
122
221
  onSelectFile: vi.fn(),
123
222
  onOpenFileViewer: vi.fn(),
124
- onCloseFile: vi.fn(),
223
+ onCloseTab: vi.fn(),
125
224
  onSelectOverview: vi.fn(),
126
225
  onSelectChildSessions: vi.fn(),
127
226
  onSelectProjectFiles: vi.fn(),
@@ -150,6 +249,7 @@ describe("buildWorkspaceTabsViewModel", () => {
150
249
  const tabs = buildWorkspaceTabsViewModel({
151
250
  resolvedChildTabs: [childTab],
152
251
  activeSideChatDraft: draft,
252
+ closedWorkspaceTabEntries: [],
153
253
  workspaceFileTabs: [],
154
254
  activeSelection: {
155
255
  kind: "side-chat-draft",
@@ -159,7 +259,7 @@ describe("buildWorkspaceTabsViewModel", () => {
159
259
  onSelectSession: vi.fn(),
160
260
  onSelectFile: vi.fn(),
161
261
  onOpenFileViewer: vi.fn(),
162
- onCloseFile: vi.fn(),
262
+ onCloseTab: vi.fn(),
163
263
  onSelectOverview: vi.fn(),
164
264
  onSelectChildSessions: vi.fn(),
165
265
  onSelectProjectFiles: vi.fn(),
@@ -202,6 +302,7 @@ describe("buildWorkspaceTabsViewModel", () => {
202
302
  const tabs = buildWorkspaceTabsViewModel({
203
303
  resolvedChildTabs: [],
204
304
  activeSideChatDraft: null,
305
+ closedWorkspaceTabEntries: [],
205
306
  workspaceFileTabs: [sourceTab, renderedTab],
206
307
  activeSelection: {
207
308
  kind: "file",
@@ -211,7 +312,7 @@ describe("buildWorkspaceTabsViewModel", () => {
211
312
  onSelectSession: vi.fn(),
212
313
  onSelectFile: vi.fn(),
213
314
  onOpenFileViewer,
214
- onCloseFile: vi.fn(),
315
+ onCloseTab: vi.fn(),
215
316
  onSelectOverview: vi.fn(),
216
317
  onSelectChildSessions: vi.fn(),
217
318
  onSelectProjectFiles: vi.fn(),