@nextclaw/ui 0.15.4 → 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 (226) hide show
  1. package/CHANGELOG.md +17 -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-BOXOrpcJ.js → api-IlX_CbwU.js} +2 -2
  5. package/dist/assets/app-presenter-provider-Bbl_MVbg.js +106 -0
  6. package/dist/assets/{appearance-settings-page-fLIJbx1C.js → appearance-settings-page-80fLa4-9.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-CN7Shpij.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-R7M6pzp6.js} +1 -1
  16. package/dist/assets/{chat-page-B42UUIFR.js → chat-page-152YuwfU.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-BlfvfmTm.js} +1 -1
  60. package/dist/assets/{confirm-dialog-GSXIvoRU.js → confirm-dialog-Dt8Kdbgp.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-7bmNeWIC.js → desktop-update-config-DreMJu9z.js} +1 -1
  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-D5ycbwCo.js} +1 -1
  75. package/dist/assets/dist-dw-gAfM_.js +1 -0
  76. package/dist/assets/{doc-browser-D0monHjG.js → doc-browser-C9u7N9vH.js} +1 -1
  77. package/dist/assets/doc-browser-DJQ5XPsi2.js +1 -0
  78. package/dist/assets/{doc-browser-context-CGwEOhBy.js → doc-browser-context-CCdW9TJf.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-CyPgglKr.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-BahW1sl3.js} +1 -1
  85. package/dist/assets/{eye-suvKmtwn.js → eye-D8gGXMkD.js} +1 -1
  86. package/dist/assets/flowDiagram-23GEKE2U-BoKk8RRq.js +1 -0
  87. package/dist/assets/{folder-CMELbQGG.js → folder-DGWqFDvE.js} +1 -1
  88. package/dist/assets/{form-actions-CUAzpNe_.js → form-actions-D2imZLnU.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-0av6WO9-.css +1 -0
  94. package/dist/assets/index-CT0iYlxV.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-DtKek-qG.js +1 -0
  105. package/dist/assets/{logo-badge-Cu5UGH_4.js → logo-badge-CuPetu-v.js} +1 -1
  106. package/dist/assets/map-Bdri5UL5.js +1 -0
  107. package/dist/assets/mcp-marketplace-page-4qMwFDpz.js +1 -0
  108. package/dist/assets/{mcp-marketplace-page-BUzHDwW6.js → mcp-marketplace-page-BbxN5aZF.js} +1 -1
  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-DiJcvBML.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-BwgE-Zwf.js +1 -0
  120. package/dist/assets/{popover-DImguRok.js → popover-Btp7KVc3.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-DZMtoAJG.js} +1 -1
  123. package/dist/assets/{providers-config-page-BKPo8SjH.js → providers-config-page-DfiWveEF.js} +1 -1
  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-BY-U3h3t.js} +1 -1
  132. package/dist/assets/{refresh-cw-CHLBzWWj.js → refresh-cw-Cme5qFej.js} +1 -1
  133. package/dist/assets/remote-BaVECEZw.js +1 -0
  134. package/dist/assets/requirementDiagram-TGXJPOKE-EmFph0H_.js +84 -0
  135. package/dist/assets/rotate-cw-CMRZxIkH.js +1 -0
  136. package/dist/assets/rough.esm-wJ1d3nQl.js +1 -0
  137. package/dist/assets/{runtime-config-page-1Axv4QGu.js → runtime-config-page-wygwvMB4.js} +1 -1
  138. package/dist/assets/sankeyDiagram-HTMAVEWB-BXWB5Ugi.js +40 -0
  139. package/dist/assets/{save-BABzyrjB.js → save-DNg2yJTS.js} +1 -1
  140. package/dist/assets/{search-BTqxG3ZG.js → search-3ykfrAFt.js} +1 -1
  141. package/dist/assets/{search-config-page-BkKKmxmS.js → search-config-page-DrVKa_Ls.js} +1 -1
  142. package/dist/assets/{secrets-config-page-CWJMyu-k.js → secrets-config-page-B39uQ5kF.js} +1 -1
  143. package/dist/assets/security-config-BPzet76z.js +1 -0
  144. package/dist/assets/{select-TYZDBNmq.js → select-B5EecXJt.js} +1 -1
  145. package/dist/assets/sequenceDiagram-DBY2YBRQ-ClKnO7v0.js +162 -0
  146. package/dist/assets/{setting-row-DR23Z_5I.js → setting-row-Qv1Ps79v.js} +1 -1
  147. package/dist/assets/{settings-2-D8WWcBnw.js → settings-2-D-3um9Az.js} +1 -1
  148. package/dist/assets/sizeCapture-X5ZJPWSS-DSo-t5ou.js +1 -0
  149. package/dist/assets/{skeleton-CloHeg0S.js → skeleton-BgkzQd4U.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-BVD8gp3y.js} +1 -1
  156. package/dist/assets/{tag-chip-mCc23Rhb.js → tag-chip-0qn-f3Bo.js} +1 -1
  157. package/dist/assets/timeline-definition-FHXFAJF6-D4ar8IQY.js +120 -0
  158. package/dist/assets/{trash-2-SAW1Z4Po.js → trash-2-cnsMppRC.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-BucdaK23.js → use-config-D6yx0lb0.js} +1 -1
  162. package/dist/assets/{use-confirm-dialog-C95F6sT1.js → use-confirm-dialog-C_o0eQeH.js} +1 -1
  163. package/dist/assets/{use-viewport-layout-C6m3Nte7.js → use-viewport-layout-BWav4KCL.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-B3o1dWR5.js +1 -0
  168. package/dist/assets/xychartDiagram-FW5EYKEG-C9JCzsZF.js +7 -0
  169. package/dist/index.html +33 -32
  170. package/package.json +5 -5
  171. package/src/features/chat/components/chat-popover-content.tsx +1 -26
  172. package/src/features/chat/components/conversation/chat-conversation-workspace-section.tsx +1 -0
  173. package/src/features/chat/features/conversation/components/session-conversation-input.tsx +28 -11
  174. package/src/features/chat/features/input/input-surface-plugins/__tests__/slash-command-plugin.test.ts +43 -0
  175. package/src/features/chat/features/input/input-surface-plugins/slash-command-plugin.utils.ts +14 -10
  176. package/src/features/chat/features/input/utils/__tests__/chat-input-bar.utils.test.ts +51 -0
  177. package/src/features/chat/features/input/utils/chat-input-bar.utils.ts +61 -21
  178. package/src/features/chat/features/message/components/__tests__/chat-message-list.container.test.tsx +42 -10
  179. package/src/features/chat/features/message/components/chat-inline-file-preview.tsx +80 -0
  180. package/src/features/chat/features/message/components/chat-message-list.container.tsx +55 -60
  181. package/src/features/chat/features/message/utils/chat-message-texts.utils.ts +2 -0
  182. package/src/features/chat/features/workspace/components/__tests__/chat-session-workspace-file-preview.test.tsx +22 -24
  183. package/src/features/chat/features/workspace/components/__tests__/chat-session-workspace-panel.test.tsx +2 -1
  184. package/src/features/chat/features/workspace/components/chat-session-workspace-file-breadcrumbs.tsx +6 -17
  185. package/src/features/chat/features/workspace/components/chat-session-workspace-file-preview.tsx +41 -19
  186. package/src/features/chat/features/workspace/components/chat-session-workspace-panel-nav.tsx +5 -1
  187. package/src/features/chat/features/workspace/components/chat-session-workspace-panel.tsx +5 -1
  188. package/src/features/chat/features/workspace/utils/__tests__/chat-workspace-panel-view-model.utils.test.ts +104 -3
  189. package/src/features/chat/features/workspace/utils/chat-thread-workspace-session.utils.ts +83 -0
  190. package/src/features/chat/features/workspace/utils/chat-workspace-file-tab.utils.ts +4 -1
  191. package/src/features/chat/features/workspace/utils/chat-workspace-panel-view-model.utils.ts +96 -25
  192. package/src/features/chat/managers/__tests__/chat-thread-workspace.manager.test.ts +25 -0
  193. package/src/features/chat/managers/__tests__/chat-thread.manager.test.ts +5 -1
  194. package/src/features/chat/managers/chat-session-list.manager.ts +1 -0
  195. package/src/features/chat/managers/chat-thread.manager.ts +15 -37
  196. package/src/features/chat/stores/__tests__/chat-thread.store.test.ts +9 -0
  197. package/src/features/chat/stores/chat-thread.store.ts +16 -36
  198. package/src/features/chat/types/chat-input-bar.types.ts +2 -0
  199. package/src/shared/components/common/__tests__/brand-header.test.tsx +17 -7
  200. package/src/shared/components/common/brand-header.tsx +34 -34
  201. package/src/shared/hooks/use-server-path-read.ts +10 -5
  202. package/src/shared/lib/api/types.ts +3 -3
  203. package/src/shared/lib/api/utils/server-path.utils.ts +1 -0
  204. package/src/shared/lib/i18n/locales/en-US/chat.json +8 -0
  205. package/src/shared/lib/i18n/locales/zh-CN/chat.json +8 -0
  206. package/src/shared/lib/session-project/__tests__/workspace-file-breadcrumb.test.ts +1 -4
  207. package/src/shared/lib/session-project/workspace-file-breadcrumb.utils.ts +1 -18
  208. package/dist/assets/app-presenter-provider-CkHLbvZQ.js +0 -106
  209. package/dist/assets/doc-browser-CgdoLUT92.js +0 -1
  210. package/dist/assets/index-BFeNDQ4R.css +0 -1
  211. package/dist/assets/index-D_6U-M94.js +0 -2
  212. package/dist/assets/loader-circle-tYHStefV.js +0 -1
  213. package/dist/assets/mcp-marketplace-page-ohUAdfqJ.js +0 -1
  214. package/dist/assets/plus-tywizkeY.js +0 -1
  215. package/dist/assets/remote-DXJryUxB.js +0 -1
  216. package/dist/assets/rotate-cw-BefO5brU.js +0 -1
  217. package/dist/assets/security-config-DeQUIXfi.js +0 -1
  218. package/dist/assets/x-DtCPHwCu.js +0 -1
  219. package/src/features/chat/components/__tests__/chat-popover-content.test.tsx +0 -41
  220. package/src/features/chat/components/layout/__tests__/chat-sidebar-utility-menu.test.tsx +0 -55
  221. /package/dist/assets/{config-hints-fDrYfl0l.js → config-hints-LxG80Uj5.js} +0 -0
  222. /package/dist/assets/{host-capabilities-zZvY9U63.js → host-capabilities-CFbAfrrA.js} +0 -0
  223. /package/dist/assets/{jszip.min-Das2IAnb.js → jszip.min-CP5uPNQa.js} +0 -0
  224. /package/dist/assets/{middleware-BLMHv9YF.js → middleware-CAXxl-af.js} +0 -0
  225. /package/dist/assets/{provider-models-C_yOh6DE.js → provider-models-BoUhFo9R.js} +0 -0
  226. /package/dist/assets/{xlsx-B6zVt1RK.js → xlsx-DUqJhg5u.js} +0 -0
@@ -24,6 +24,7 @@ describe('ChatThreadManager workspace pages', () => {
24
24
  activeWorkspacePanelKind: null,
25
25
  activeChildSessionKey: null,
26
26
  activeWorkspaceFileKey: null,
27
+ closedWorkspaceTabEntries: [],
27
28
  workspaceNavigationHistory: [],
28
29
  workspaceNavigationHistoryIndex: 0,
29
30
  },
@@ -73,4 +74,28 @@ describe('ChatThreadManager workspace pages', () => {
73
74
  activeWorkspacePanelKind: null,
74
75
  });
75
76
  });
77
+
78
+ it('closes a workspace page, restores overview, and reopens the page on demand', () => {
79
+ const manager = new ChatThreadManager(
80
+ createUiManager(),
81
+ {} as ConstructorParameters<typeof ChatThreadManager>[1],
82
+ );
83
+
84
+ manager.openWorkspaceOverview('parent-session-1');
85
+ manager.openProjectFiles('parent-session-1');
86
+ manager.closeWorkspaceTab({ kind: 'project-files' });
87
+
88
+ expect(useChatThreadStore.getState().snapshot).toMatchObject({
89
+ activeWorkspacePanelKind: 'overview',
90
+ closedWorkspaceTabEntries: [{ kind: 'project-files' }],
91
+ workspaceNavigationHistory: [{ kind: 'overview' }],
92
+ workspaceNavigationHistoryIndex: 0,
93
+ });
94
+
95
+ manager.openProjectFiles('parent-session-1');
96
+ expect(useChatThreadStore.getState().snapshot).toMatchObject({
97
+ activeWorkspacePanelKind: 'project-files',
98
+ closedWorkspaceTabEntries: [],
99
+ });
100
+ });
76
101
  });
@@ -60,6 +60,7 @@ beforeEach(() => {
60
60
  activeSideChatDraft: null,
61
61
  workspaceFileTabs: [],
62
62
  activeWorkspaceFileKey: null,
63
+ closedWorkspaceTabEntries: [],
63
64
  workspaceNavigationHistory: [],
64
65
  workspaceNavigationHistoryIndex: 0,
65
66
  },
@@ -504,7 +505,10 @@ describe('ChatThreadManager workspace navigation', () => {
504
505
  label: 'README.md',
505
506
  viewMode: 'preview',
506
507
  });
507
- manager.closeWorkspaceFile('parent-session-1::preview:rendered::README.md');
508
+ manager.closeWorkspaceTab({
509
+ kind: 'file',
510
+ key: 'parent-session-1::preview:rendered::README.md',
511
+ });
508
512
 
509
513
  expect(useChatThreadStore.getState().snapshot).toMatchObject({
510
514
  activeWorkspacePanelKind: 'child-session',
@@ -39,6 +39,7 @@ export class ChatSessionListManager {
39
39
  childSessionTabs: [],
40
40
  activeChildSessionKey: null,
41
41
  activeWorkspaceFileKey: null,
42
+ closedWorkspaceTabEntries: [],
42
43
  workspaceNavigationHistory: [],
43
44
  workspaceNavigationHistoryIndex: 0,
44
45
  });
@@ -24,13 +24,13 @@ import { normalizeChatWorkspacePanelWidth } from '@/features/chat/features/works
24
24
  import { t } from '@/shared/lib/i18n';
25
25
  import {
26
26
  areWorkspaceNavigationEntriesEqual,
27
+ closeWorkspaceTabSnapshot,
27
28
  createWorkspaceSelectionPatch,
28
29
  createSideChatDraft,
29
30
  materializeSideChatDraftSnapshot,
30
31
  upsertChildSessionTab,
31
32
  } from '@/features/chat/features/workspace/utils/chat-thread-workspace-session.utils';
32
33
  import {
33
- filterNavigationHistoryEntries,
34
34
  pushNavigationHistoryEntry,
35
35
  stepNavigationHistory,
36
36
  } from '@/shared/lib/navigation-history';
@@ -96,6 +96,7 @@ export class ChatThreadManager {
96
96
  activeSideChatDraft: null,
97
97
  workspaceFileTabs: [],
98
98
  activeWorkspaceFileKey: null,
99
+ closedWorkspaceTabEntries: [],
99
100
  workspaceNavigationHistory: [],
100
101
  workspaceNavigationHistoryIndex: 0,
101
102
  });
@@ -163,8 +164,16 @@ export class ChatThreadManager {
163
164
  entry,
164
165
  areWorkspaceNavigationEntriesEqual,
165
166
  );
167
+ const closedWorkspaceTabEntries =
168
+ patch.workspacePanelParentKey &&
169
+ patch.workspacePanelParentKey !== snapshot.workspacePanelParentKey
170
+ ? []
171
+ : snapshot.closedWorkspaceTabEntries.filter(
172
+ (candidate) => !areWorkspaceNavigationEntriesEqual(candidate, entry),
173
+ );
166
174
  useChatThreadStore.getState().setSnapshot({
167
175
  ...patch,
176
+ closedWorkspaceTabEntries,
168
177
  workspaceNavigationHistory: [...history.entries],
169
178
  workspaceNavigationHistoryIndex: history.index,
170
179
  });
@@ -461,44 +470,12 @@ export class ChatThreadManager {
461
470
  });
462
471
  };
463
472
 
464
- closeWorkspaceFile = (fileKey: string) => {
465
- const normalizedFileKey = fileKey.trim();
466
- if (!normalizedFileKey) {
467
- return;
468
- }
473
+ closeWorkspaceTab = (entry: ChatWorkspaceNavigationEntry) => {
469
474
  const { snapshot } = useChatThreadStore.getState();
470
- const { activeWorkspaceFileKey, workspaceFileTabs } = snapshot;
471
- const nextTabs = workspaceFileTabs.filter(
472
- (tab) => tab.key !== normalizedFileKey,
473
- );
474
- const history = filterNavigationHistoryEntries(
475
- {
476
- entries: snapshot.workspaceNavigationHistory,
477
- index: snapshot.workspaceNavigationHistoryIndex,
478
- },
479
- (entry) => entry.kind !== 'file' || entry.key !== normalizedFileKey,
480
- );
481
- const nextPatch: Partial<ChatThreadSnapshot> = {
482
- workspaceFileTabs: nextTabs,
483
- workspaceNavigationHistory: [...history.entries],
484
- workspaceNavigationHistoryIndex: history.index,
485
- };
486
- if (activeWorkspaceFileKey === normalizedFileKey) {
487
- const nextSnapshot = {
488
- ...snapshot,
489
- workspaceFileTabs: nextTabs,
490
- };
491
- const restoreEntry = history.entries[history.index];
492
- const restorePatch = restoreEntry
493
- ? createWorkspaceSelectionPatch(restoreEntry, nextSnapshot)
494
- : null;
495
- Object.assign(nextPatch, restorePatch ?? {
496
- activeWorkspacePanelKind: null,
497
- activeSideChatDraft: null,
498
- activeWorkspaceFileKey: null,
499
- });
475
+ const patch = closeWorkspaceTabSnapshot(snapshot, entry);
476
+ if (patch) {
477
+ useChatThreadStore.getState().setSnapshot(patch);
500
478
  }
501
- useChatThreadStore.getState().setSnapshot(nextPatch);
502
479
  };
503
480
 
504
481
  closeWorkspacePanel = () => {
@@ -508,6 +485,7 @@ export class ChatThreadManager {
508
485
  activeChildSessionKey: null,
509
486
  activeSideChatDraft: null,
510
487
  activeWorkspaceFileKey: null,
488
+ closedWorkspaceTabEntries: [],
511
489
  workspaceNavigationHistory: [],
512
490
  workspaceNavigationHistoryIndex: 0,
513
491
  });
@@ -28,6 +28,7 @@ function resetChatThreadStore() {
28
28
  activeSideChatDraft: null,
29
29
  workspaceFileTabs: [],
30
30
  activeWorkspaceFileKey: null,
31
+ closedWorkspaceTabEntries: [],
31
32
  workspaceNavigationHistory: [],
32
33
  workspaceNavigationHistoryIndex: 0,
33
34
  workspacePanelWidth: CHAT_WORKSPACE_PANEL_DEFAULT_WIDTH,
@@ -64,6 +65,10 @@ describe('chat thread workspace panel persistence', () => {
64
65
  },
65
66
  ],
66
67
  activeWorkspaceFileKey: 'session-1::preview::README.md',
68
+ closedWorkspaceTabEntries: [
69
+ { kind: 'project-files' },
70
+ { kind: 'child-session', key: 'child-session-2' },
71
+ ],
67
72
  workspaceNavigationHistory: [
68
73
  { kind: 'child-session', key: 'child-session-1' },
69
74
  { kind: 'file', key: 'session-1::preview::README.md' },
@@ -79,6 +84,10 @@ describe('chat thread workspace panel persistence', () => {
79
84
  workspacePanelParentKey: 'session-1',
80
85
  activeWorkspacePanelKind: 'file',
81
86
  activeWorkspaceFileKey: 'session-1::preview::README.md',
87
+ closedWorkspaceTabEntries: [
88
+ { kind: 'project-files' },
89
+ { kind: 'child-session', key: 'child-session-2' },
90
+ ],
82
91
  workspaceNavigationHistory: [
83
92
  { kind: 'child-session', key: 'child-session-1' },
84
93
  { kind: 'file', key: 'session-1::preview::README.md' },
@@ -15,6 +15,7 @@ import {
15
15
  CHAT_WORKSPACE_PANEL_DEFAULT_WIDTH,
16
16
  normalizeChatWorkspacePanelWidth,
17
17
  } from '@/features/chat/features/workspace/utils/chat-workspace-panel-layout.utils';
18
+ import { createWorkspaceNavigationEntryFromSnapshot } from '@/features/chat/features/workspace/utils/chat-thread-workspace-session.utils';
18
19
 
19
20
  export type ChatChildSessionTab = {
20
21
  sessionKey: string;
@@ -92,6 +93,7 @@ export type ChatThreadSnapshot = {
92
93
  activeSideChatDraft?: ChatWorkspaceSideChatDraft | null;
93
94
  workspaceFileTabs: ChatWorkspaceFileTab[];
94
95
  activeWorkspaceFileKey?: string | null;
96
+ closedWorkspaceTabEntries: ChatWorkspaceNavigationEntry[];
95
97
  workspaceNavigationHistory: ChatWorkspaceNavigationEntry[];
96
98
  workspaceNavigationHistoryIndex: number;
97
99
  workspacePanelWidth: number;
@@ -114,6 +116,7 @@ type PersistedChatThreadStore = {
114
116
  activeChildSessionKey?: unknown;
115
117
  workspaceFileTabs?: unknown;
116
118
  activeWorkspaceFileKey?: unknown;
119
+ closedWorkspaceTabEntries?: unknown;
117
120
  workspaceNavigationHistory?: unknown;
118
121
  workspaceNavigationHistoryIndex?: unknown;
119
122
  workspacePanelWidth?: unknown;
@@ -127,6 +130,7 @@ type PersistedChatWorkspaceSnapshot = Pick<
127
130
  | 'activeChildSessionKey'
128
131
  | 'workspaceFileTabs'
129
132
  | 'activeWorkspaceFileKey'
133
+ | 'closedWorkspaceTabEntries'
130
134
  | 'workspaceNavigationHistory'
131
135
  | 'workspaceNavigationHistoryIndex'
132
136
  | 'workspacePanelWidth'
@@ -159,6 +163,7 @@ const initialSnapshot: ChatThreadSnapshot = {
159
163
  activeSideChatDraft: null,
160
164
  workspaceFileTabs: [],
161
165
  activeWorkspaceFileKey: null,
166
+ closedWorkspaceTabEntries: [],
162
167
  workspaceNavigationHistory: [],
163
168
  workspaceNavigationHistoryIndex: 0,
164
169
  workspacePanelWidth: CHAT_WORKSPACE_PANEL_DEFAULT_WIDTH,
@@ -218,42 +223,6 @@ function normalizePersistedWorkspaceNavigationEntry(
218
223
  };
219
224
  }
220
225
 
221
- function createWorkspaceNavigationEntryFromSnapshot({
222
- activeChildSessionKey,
223
- activeWorkspaceFileKey,
224
- activeWorkspacePanelKind,
225
- }: {
226
- activeWorkspacePanelKind: ChatThreadSnapshot['activeWorkspacePanelKind'];
227
- activeChildSessionKey: string | null;
228
- activeWorkspaceFileKey: string | null;
229
- }): ChatWorkspaceNavigationEntry | null {
230
- if (activeWorkspacePanelKind === 'overview') {
231
- return { kind: 'overview' };
232
- }
233
- if (activeWorkspacePanelKind === 'child-sessions') {
234
- return { kind: 'child-sessions' };
235
- }
236
- if (activeWorkspacePanelKind === 'project-files') {
237
- return { kind: 'project-files' };
238
- }
239
- if (activeWorkspacePanelKind === 'cron') {
240
- return { kind: 'cron' };
241
- }
242
- if (activeWorkspacePanelKind === 'child-session' && activeChildSessionKey) {
243
- return {
244
- kind: 'child-session',
245
- key: activeChildSessionKey,
246
- };
247
- }
248
- if (activeWorkspacePanelKind === 'file' && activeWorkspaceFileKey) {
249
- return {
250
- kind: 'file',
251
- key: activeWorkspaceFileKey,
252
- };
253
- }
254
- return null;
255
- }
256
-
257
226
  function normalizePersistedWorkspaceSnapshot(
258
227
  value: unknown,
259
228
  ): PersistedChatWorkspaceSnapshot | null {
@@ -287,9 +256,17 @@ function normalizePersistedWorkspaceSnapshot(
287
256
  ? null
288
257
  : activeWorkspacePanelKind;
289
258
  const activeChildSessionKey = normalizeOptionalString(value.activeChildSessionKey);
259
+ const closedWorkspaceTabEntries = Array.isArray(value.closedWorkspaceTabEntries)
260
+ ? value.closedWorkspaceTabEntries
261
+ .map(normalizePersistedWorkspaceNavigationEntry)
262
+ .filter((entry): entry is ChatWorkspaceNavigationEntry =>
263
+ entry !== null && entry.kind !== 'overview',
264
+ )
265
+ : [];
290
266
  const fallbackHistoryEntry = createWorkspaceNavigationEntryFromSnapshot({
291
267
  activeWorkspacePanelKind: resolvedActiveWorkspacePanelKind,
292
268
  activeChildSessionKey,
269
+ activeSideChatDraft: null,
293
270
  activeWorkspaceFileKey: resolvedActiveWorkspaceFileKey,
294
271
  });
295
272
  const normalizedNavigationHistory = Array.isArray(value.workspaceNavigationHistory)
@@ -320,6 +297,7 @@ function normalizePersistedWorkspaceSnapshot(
320
297
  activeChildSessionKey,
321
298
  workspaceFileTabs,
322
299
  activeWorkspaceFileKey: resolvedActiveWorkspaceFileKey,
300
+ closedWorkspaceTabEntries,
323
301
  workspaceNavigationHistory,
324
302
  workspaceNavigationHistoryIndex,
325
303
  workspacePanelWidth: normalizeChatWorkspacePanelWidth(
@@ -369,6 +347,8 @@ export const useChatThreadStore = create<ChatThreadStore>()(
369
347
  )
370
348
  .map(toPersistedWorkspaceFileTab),
371
349
  activeWorkspaceFileKey: state.snapshot.activeWorkspaceFileKey,
350
+ closedWorkspaceTabEntries: state.snapshot.closedWorkspaceTabEntries
351
+ .filter((entry) => entry.kind !== 'overview' && entry.kind !== 'side-chat-draft'),
372
352
  workspaceNavigationHistory,
373
353
  workspaceNavigationHistoryIndex,
374
354
  workspacePanelWidth: state.snapshot.workspacePanelWidth,
@@ -11,6 +11,8 @@ export type ChatSkillRecord = {
11
11
  key: string;
12
12
  label: string;
13
13
  scopeLabel?: string;
14
+ groupKey?: string;
15
+ groupLabel?: string;
14
16
  description?: string;
15
17
  descriptionZh?: string;
16
18
  badgeLabel?: string;
@@ -100,7 +100,7 @@ describe('BrandHeader', () => {
100
100
  expect(version).toBeTruthy();
101
101
  expect(screen.getAllByText('v0.18.11')).toHaveLength(1);
102
102
  await user.hover(version);
103
- expect(await screen.findAllByText('v0.18.11')).toHaveLength(2);
103
+ expect((await screen.findByRole('tooltip')).textContent).toBe('v0.18.11');
104
104
  expect(screen.getByText('下载 50%')).toBeTruthy();
105
105
  expect(screen.queryByRole('button', { name: '更新' })).toBeNull();
106
106
  });
@@ -145,6 +145,7 @@ describe('BrandHeader', () => {
145
145
  });
146
146
 
147
147
  it('shows a warning icon with the blocked update reason instead of a visible failure label', async () => {
148
+ const user = userEvent.setup();
148
149
  useRuntimeUpdateStore.setState({
149
150
  supported: true,
150
151
  initialized: true,
@@ -180,13 +181,19 @@ describe('BrandHeader', () => {
180
181
  const issueIcon = screen.getByLabelText('更新被阻塞');
181
182
 
182
183
  expect(issueIcon.textContent).toBe('!');
183
- expect(issueIcon.getAttribute('title')).toContain('更新被阻塞');
184
- expect(issueIcon.getAttribute('title')).toContain('根因:缺少更新签名公钥,无法验证更新包来源');
185
- expect(issueIcon.getAttribute('title')).toContain('Runtime bundle updates require a configured update public key.');
186
- expect(issueIcon.getAttribute('title')).toContain('Set NEXTCLAW_UPDATE_BUNDLE_PUBLIC_KEY');
184
+ expect(issueIcon.hasAttribute('title')).toBe(false);
185
+
186
+ await user.hover(issueIcon);
187
+
188
+ const tooltip = await screen.findByRole('tooltip');
189
+ expect(tooltip.textContent).toContain('更新被阻塞');
190
+ expect(tooltip.textContent).toContain('根因:缺少更新签名公钥,无法验证更新包来源');
191
+ expect(tooltip.textContent).toContain('Runtime bundle updates require a configured update public key.');
192
+ expect(tooltip.textContent).toContain('Set NEXTCLAW_UPDATE_BUNDLE_PUBLIC_KEY');
187
193
  });
188
194
 
189
195
  it('uses the failed update wording only for failed snapshots', async () => {
196
+ const user = userEvent.setup();
190
197
  useRuntimeUpdateStore.setState({
191
198
  supported: true,
192
199
  initialized: true,
@@ -221,8 +228,11 @@ describe('BrandHeader', () => {
221
228
  const issueIcon = screen.getByLabelText('更新失败');
222
229
 
223
230
  expect(issueIcon.textContent).toBe('!');
224
- expect(issueIcon.getAttribute('title')).toContain('更新失败');
225
- expect(issueIcon.getAttribute('title')).toContain('runtime bundle sha256 mismatch');
231
+ await user.hover(issueIcon);
232
+
233
+ const tooltip = await screen.findByRole('tooltip');
234
+ expect(tooltip.textContent).toContain('更新失败');
235
+ expect(tooltip.textContent).toContain('runtime bundle sha256 mismatch');
226
236
  expect(screen.queryByText('更新被阻塞')).toBeNull();
227
237
  });
228
238
  });
@@ -1,8 +1,9 @@
1
1
  import type { UpdateSnapshot } from '@nextclaw/shared';
2
2
  import { runtimeUpdateManager, useRuntimeUpdateStore } from '@/features/system-status';
3
3
  import { useAppMeta } from '@/shared/hooks/use-app-meta';
4
- import { type ReactNode, useState } from 'react';
4
+ import type { ReactNode } from 'react';
5
5
  import { RuntimeStatusEntry } from '@/app/components/layout/runtime-status-entry';
6
+ import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/shared/components/ui/tooltip';
6
7
  import { t } from '@/shared/lib/i18n';
7
8
  import { cn } from '@/shared/lib/utils';
8
9
 
@@ -57,33 +58,26 @@ function BrandVersionLabel({
57
58
  versionLabel: string;
58
59
  density: BrandHeaderProps['density'];
59
60
  }) {
60
- const [isTooltipOpen, setIsTooltipOpen] = useState(false);
61
61
  const isChromeDensity = density === 'chrome';
62
62
 
63
63
  return (
64
- <span
65
- className="relative min-w-0 flex-1"
66
- onMouseEnter={() => setIsTooltipOpen(true)}
67
- onMouseLeave={() => setIsTooltipOpen(false)}
68
- onFocus={() => setIsTooltipOpen(true)}
69
- onBlur={() => setIsTooltipOpen(false)}
70
- >
71
- <span
72
- tabIndex={0}
73
- aria-label={versionLabel}
74
- className={cn(
75
- 'block min-w-0 truncate font-medium text-gray-500 outline-none',
76
- isChromeDensity ? 'text-[12px]' : 'text-[12px]',
77
- )}
78
- >
79
- {versionLabel}
80
- </span>
81
- {isTooltipOpen ? (
82
- <span className="pointer-events-none absolute left-0 top-full z-[var(--z-tooltip)] mt-1 w-max max-w-none whitespace-nowrap rounded-md border bg-popover px-3 py-1.5 text-xs font-medium text-popover-foreground shadow-md">
83
- {versionLabel}
84
- </span>
85
- ) : null}
86
- </span>
64
+ <TooltipProvider delayDuration={250}>
65
+ <Tooltip>
66
+ <TooltipTrigger asChild>
67
+ <span
68
+ tabIndex={0}
69
+ aria-label={versionLabel}
70
+ className={cn(
71
+ 'block min-w-0 flex-1 truncate font-medium text-gray-500 outline-none',
72
+ isChromeDensity ? 'text-[12px]' : 'text-[12px]',
73
+ )}
74
+ >
75
+ {versionLabel}
76
+ </span>
77
+ </TooltipTrigger>
78
+ <TooltipContent side="bottom" className="text-xs">{versionLabel}</TooltipContent>
79
+ </Tooltip>
80
+ </TooltipProvider>
87
81
  );
88
82
  }
89
83
 
@@ -149,15 +143,21 @@ function RuntimeUpdateIssueIcon({ snapshot }: { snapshot: UpdateSnapshot }) {
149
143
  : null;
150
144
  const tooltip = [title, rootCause, diagnostic, recoveryCommand].filter(Boolean).join('\n');
151
145
  return (
152
- <span
153
- role="img"
154
- aria-label={title}
155
- title={tooltip}
156
- tabIndex={0}
157
- className="inline-flex h-5 w-5 shrink-0 items-center justify-center rounded-full bg-amber-50 text-[13px] font-bold leading-none text-amber-700 ring-1 ring-amber-100"
158
- >
159
- !
160
- </span>
146
+ <TooltipProvider delayDuration={250}>
147
+ <Tooltip>
148
+ <TooltipTrigger asChild>
149
+ <span
150
+ role="img"
151
+ aria-label={title}
152
+ tabIndex={0}
153
+ className="inline-flex h-5 w-5 shrink-0 items-center justify-center rounded-full bg-amber-50 text-[13px] font-bold leading-none text-amber-700 ring-1 ring-amber-100 outline-none focus-visible:ring-2 focus-visible:ring-amber-300"
154
+ >
155
+ !
156
+ </span>
157
+ </TooltipTrigger>
158
+ <TooltipContent side="bottom" className="max-w-80 whitespace-pre-line break-words text-xs leading-relaxed">{tooltip}</TooltipContent>
159
+ </Tooltip>
160
+ </TooltipProvider>
161
161
  );
162
162
  }
163
163
 
@@ -4,27 +4,32 @@ import { fetchServerPathRead } from '@/shared/lib/api';
4
4
  export function buildServerPathReadQueryKey(params: {
5
5
  path?: string | null;
6
6
  basePath?: string | null;
7
+ line?: number | null;
7
8
  }) {
8
- return ['server-path-read', params.path?.trim() ?? '', params.basePath ?? null] as const;
9
+ return ['server-path-read', params.path?.trim() ?? '', params.basePath ?? null, params.line ?? null] as const;
9
10
  }
10
11
 
11
12
  export function useServerPathRead(params: {
12
13
  path?: string | null;
13
14
  basePath?: string | null;
15
+ line?: number | null;
14
16
  enabled?: boolean;
15
17
  }) {
16
- const normalizedPath = params.path?.trim() ?? '';
18
+ const { basePath, enabled = true, line, path } = params;
19
+ const normalizedPath = path?.trim() ?? '';
17
20
  return useQuery({
18
21
  queryKey: buildServerPathReadQueryKey({
19
22
  path: normalizedPath,
20
- basePath: params.basePath,
23
+ basePath,
24
+ line,
21
25
  }),
22
26
  queryFn: () =>
23
27
  fetchServerPathRead({
24
28
  path: normalizedPath,
25
- basePath: params.basePath,
29
+ basePath,
30
+ line,
26
31
  }),
27
- enabled: (params.enabled ?? true) && normalizedPath.length > 0,
32
+ enabled: enabled && normalizedPath.length > 0,
28
33
  staleTime: 0,
29
34
  });
30
35
  }
@@ -316,8 +316,8 @@ export type SessionSkillEntryView = {
316
316
  ref: string;
317
317
  name: string;
318
318
  path: string;
319
- scope: 'builtin' | 'project' | 'workspace';
320
- source: 'builtin' | 'project' | 'workspace';
319
+ scope: 'builtin' | 'global' | 'project' | 'workspace';
320
+ source: 'builtin' | 'global' | 'project' | 'workspace';
321
321
  available: boolean;
322
322
  description?: string;
323
323
  descriptionZh?: string;
@@ -367,7 +367,7 @@ export type ServerPathBrowseView = {
367
367
  entries: ServerPathEntryView[];
368
368
  };
369
369
 
370
- export type ServerPathReadView = { requestedPath: string; resolvedPath: string; kind: "text" | "markdown" | "binary"; sizeBytes: number; truncated: boolean; text?: string; languageHint?: string | null };
370
+ export type ServerPathReadView = { requestedPath: string; resolvedPath: string; kind: "text" | "markdown" | "binary"; sizeBytes: number; startLine?: number; truncated: boolean; text?: string; languageHint?: string | null };
371
371
 
372
372
  export type PanelAppEntryView = {
373
373
  id: string;
@@ -15,6 +15,7 @@ export async function fetchServerPathBrowse(params?: {
15
15
  export async function fetchServerPathRead(params: {
16
16
  path: string;
17
17
  basePath?: string | null;
18
+ line?: number | null;
18
19
  }): Promise<ServerPathReadView> {
19
20
  return await nextclawClient.serverPaths.read(params);
20
21
  }
@@ -65,6 +65,7 @@
65
65
  "chatWorkspaceFileMoreActions": "File actions",
66
66
  "chatWorkspaceDiff": "Diff",
67
67
  "chatWorkspaceCloseFile": "Close file",
68
+ "chatWorkspaceCloseTab": "Close tab",
68
69
  "chatWorkspaceLoadingPreview": "Loading preview…",
69
70
  "chatWorkspaceLoadingFile": "Loading file…",
70
71
  "chatWorkspaceLoadingDirectory": "Loading directory…",
@@ -201,8 +202,13 @@
201
202
  "chatPanelAppReferenceFile": "File",
202
203
  "chatPanelAppReferenceNoDescription": "No panel app description",
203
204
  "chatSkillScopeBuiltin": "Built-in",
205
+ "chatSkillScopeGlobal": "Global",
204
206
  "chatSkillScopeProject": "Project",
205
207
  "chatSkillScopeWorkspace": "Workspace",
208
+ "chatSkillGroupBuiltin": "Built-in skills",
209
+ "chatSkillGroupGlobal": "Global skills",
210
+ "chatSkillGroupProject": "Project skills",
211
+ "chatSkillGroupWorkspace": "NextClaw skills",
206
212
  "chatPickerRecent": "Recent",
207
213
  "chatPickerFavoriteModels": "Favorites",
208
214
  "chatPickerRecentModels": "Recent",
@@ -296,6 +302,8 @@
296
302
  "chatCodeCopied": "Copied",
297
303
  "chatMessageCopy": "Copy",
298
304
  "chatMessageCopied": "Copied",
305
+ "chatMermaidDiagram": "Diagram",
306
+ "chatMermaidRenderError": "Diagram could not be rendered; showing source instead",
299
307
  "chatSidebarNewTask": "New Task",
300
308
  "chatSidebarSearchPlaceholder": "Search conversations...",
301
309
  "chatSidebarScheduledTasks": "Scheduled Tasks",
@@ -65,6 +65,7 @@
65
65
  "chatWorkspaceFileMoreActions": "文件操作",
66
66
  "chatWorkspaceDiff": "Diff",
67
67
  "chatWorkspaceCloseFile": "关闭文件",
68
+ "chatWorkspaceCloseTab": "关闭标签页",
68
69
  "chatWorkspaceLoadingPreview": "正在加载预览…",
69
70
  "chatWorkspaceLoadingFile": "正在加载文件…",
70
71
  "chatWorkspaceLoadingDirectory": "正在加载目录…",
@@ -201,8 +202,13 @@
201
202
  "chatPanelAppReferenceFile": "文件",
202
203
  "chatPanelAppReferenceNoDescription": "暂无面板应用描述",
203
204
  "chatSkillScopeBuiltin": "内建",
205
+ "chatSkillScopeGlobal": "全局",
204
206
  "chatSkillScopeProject": "项目",
205
207
  "chatSkillScopeWorkspace": "工作区",
208
+ "chatSkillGroupBuiltin": "内建技能",
209
+ "chatSkillGroupGlobal": "全局技能",
210
+ "chatSkillGroupProject": "项目技能",
211
+ "chatSkillGroupWorkspace": "NextClaw 技能",
206
212
  "chatPickerRecent": "最近使用",
207
213
  "chatPickerFavoriteModels": "收藏模型",
208
214
  "chatPickerRecentModels": "最近选择",
@@ -296,6 +302,8 @@
296
302
  "chatCodeCopied": "已复制",
297
303
  "chatMessageCopy": "复制",
298
304
  "chatMessageCopied": "已复制",
305
+ "chatMermaidDiagram": "图表",
306
+ "chatMermaidRenderError": "图表无法渲染,已显示源代码",
299
307
  "chatSidebarNewTask": "新任务",
300
308
  "chatSidebarSearchPlaceholder": "搜索对话...",
301
309
  "chatSidebarScheduledTasks": "定时任务",
@@ -80,16 +80,13 @@ describe("buildWorkspaceFileBreadcrumb", () => {
80
80
  ]);
81
81
  });
82
82
 
83
- it("attaches line metadata for the workspace breadcrumb bar", () => {
83
+ it("keeps truncation status separate from path segments", () => {
84
84
  const breadcrumb = buildWorkspaceFileBreadcrumb({
85
85
  path: "README.md",
86
86
  sessionProjectRoot: "/Users/demo/project-alpha",
87
- line: 12,
88
- column: 4,
89
87
  truncated: true,
90
88
  });
91
89
 
92
- expect(breadcrumb.locationLabel).toBe("L12:4");
93
90
  expect(breadcrumb.truncated).toBe(true);
94
91
  expect(breadcrumb.segments[0]?.label).toBe("project-alpha");
95
92
  expect(breadcrumb.segments[1]?.label).toBe("README.md");
@@ -11,7 +11,6 @@ export type WorkspaceFileBreadcrumbSegmentViewModel = {
11
11
 
12
12
  export type WorkspaceFileBreadcrumbViewModel = {
13
13
  fullPath: string;
14
- locationLabel: string | null;
15
14
  truncated: boolean;
16
15
  segments: WorkspaceFileBreadcrumbSegmentViewModel[];
17
16
  };
@@ -153,28 +152,13 @@ function buildSegmentsFromLabels(params: {
153
152
  return leading ? [leading, ...items] : items;
154
153
  }
155
154
 
156
- function buildLocationLabel(params: {
157
- line?: number | null;
158
- column?: number | null;
159
- }): string | null {
160
- const { column, line } = params;
161
-
162
- if (typeof line !== "number") {
163
- return null;
164
- }
165
-
166
- return `L${line}${typeof column === "number" ? `:${column}` : ""}`;
167
- }
168
-
169
155
  export function buildWorkspaceFileBreadcrumb(params: {
170
156
  path: string;
171
157
  kind?: "file" | "directory";
172
158
  sessionProjectRoot: string | null;
173
- line?: number | null;
174
- column?: number | null;
175
159
  truncated: boolean;
176
160
  }): WorkspaceFileBreadcrumbViewModel {
177
- const { column, kind = "file", line, path, sessionProjectRoot, truncated } = params;
161
+ const { kind = "file", path, sessionProjectRoot, truncated } = params;
178
162
  const fullPath = path.trim();
179
163
  const relativeSegments =
180
164
  sessionProjectRoot?.trim() && fullPath
@@ -239,7 +223,6 @@ export function buildWorkspaceFileBreadcrumb(params: {
239
223
 
240
224
  return {
241
225
  fullPath,
242
- locationLabel: buildLocationLabel({ line, column }),
243
226
  truncated,
244
227
  segments,
245
228
  };