@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
@@ -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(),
@@ -33,6 +33,37 @@ export function areWorkspaceNavigationEntriesEqual(
33
33
  );
34
34
  }
35
35
 
36
+ export function createWorkspaceNavigationEntryFromSnapshot(
37
+ snapshot: Pick<
38
+ ChatThreadSnapshot,
39
+ 'activeChildSessionKey' | 'activeSideChatDraft' | 'activeWorkspaceFileKey' | 'activeWorkspacePanelKind'
40
+ >,
41
+ ): ChatWorkspaceNavigationEntry | null {
42
+ const {
43
+ activeChildSessionKey,
44
+ activeSideChatDraft,
45
+ activeWorkspaceFileKey,
46
+ activeWorkspacePanelKind,
47
+ } = snapshot;
48
+ if (
49
+ activeWorkspacePanelKind === 'overview' ||
50
+ activeWorkspacePanelKind === 'child-sessions' ||
51
+ activeWorkspacePanelKind === 'project-files' ||
52
+ activeWorkspacePanelKind === 'cron'
53
+ ) {
54
+ return { kind: activeWorkspacePanelKind };
55
+ }
56
+ if (activeWorkspacePanelKind === 'child-session' && activeChildSessionKey) {
57
+ return { kind: 'child-session', key: activeChildSessionKey };
58
+ }
59
+ if (activeWorkspacePanelKind === 'side-chat-draft' && activeSideChatDraft) {
60
+ return { kind: 'side-chat-draft', key: activeSideChatDraft.draftKey };
61
+ }
62
+ return activeWorkspacePanelKind === 'file' && activeWorkspaceFileKey
63
+ ? { kind: 'file', key: activeWorkspaceFileKey }
64
+ : null;
65
+ }
66
+
36
67
  export function createWorkspaceSelectionPatch(
37
68
  entry: ChatWorkspaceNavigationEntry,
38
69
  snapshot: ChatThreadSnapshot,
@@ -88,6 +119,58 @@ export function createWorkspaceSelectionPatch(
88
119
  };
89
120
  }
90
121
 
122
+ export function closeWorkspaceTabSnapshot(
123
+ snapshot: ChatThreadSnapshot,
124
+ entry: ChatWorkspaceNavigationEntry,
125
+ ): Partial<ChatThreadSnapshot> | null {
126
+ if (entry.kind === 'overview') {
127
+ return null;
128
+ }
129
+ const history = filterNavigationHistoryEntries(
130
+ {
131
+ entries: snapshot.workspaceNavigationHistory,
132
+ index: snapshot.workspaceNavigationHistoryIndex,
133
+ },
134
+ (candidate) => !areWorkspaceNavigationEntriesEqual(candidate, entry),
135
+ { kind: 'overview' },
136
+ );
137
+ const shouldRememberClosure = entry.kind !== 'file' && entry.kind !== 'side-chat-draft';
138
+ const patch: Partial<ChatThreadSnapshot> = {
139
+ closedWorkspaceTabEntries: shouldRememberClosure
140
+ ? [
141
+ ...snapshot.closedWorkspaceTabEntries.filter(
142
+ (candidate) => !areWorkspaceNavigationEntriesEqual(candidate, entry),
143
+ ),
144
+ entry,
145
+ ]
146
+ : snapshot.closedWorkspaceTabEntries,
147
+ childSessionTabs: entry.kind === 'child-session'
148
+ ? snapshot.childSessionTabs.filter((tab) => tab.sessionKey !== entry.key)
149
+ : snapshot.childSessionTabs,
150
+ activeSideChatDraft: entry.kind === 'side-chat-draft'
151
+ ? null
152
+ : snapshot.activeSideChatDraft,
153
+ workspaceFileTabs: entry.kind === 'file'
154
+ ? snapshot.workspaceFileTabs.filter((tab) => tab.key !== entry.key)
155
+ : snapshot.workspaceFileTabs,
156
+ workspaceNavigationHistory: [...history.entries],
157
+ workspaceNavigationHistoryIndex: history.index,
158
+ };
159
+ const activeEntry = createWorkspaceNavigationEntryFromSnapshot(snapshot);
160
+ if (!activeEntry || !areWorkspaceNavigationEntriesEqual(activeEntry, entry)) {
161
+ return patch;
162
+ }
163
+ const restoreEntry = history.entries[history.index] ?? { kind: 'overview' };
164
+ const restorePatch = createWorkspaceSelectionPatch(restoreEntry, {
165
+ ...snapshot,
166
+ ...patch,
167
+ });
168
+ return {
169
+ ...patch,
170
+ ...(restorePatch ?? createWorkspaceSelectionPatch({ kind: 'overview' }, snapshot)),
171
+ };
172
+ }
173
+
91
174
  export function createSideChatDraft(parentSessionKey: string): ChatWorkspaceSideChatDraft {
92
175
  const randomPart =
93
176
  typeof globalThis.crypto?.randomUUID === 'function'
@@ -12,7 +12,10 @@ export function createWorkspaceFileTab(
12
12
  }
13
13
  const normalizedParentSessionKey = parentSessionKey?.trim() || null;
14
14
  const previewViewer = action.viewMode === 'preview'
15
- ? normalizeWorkspaceFilePreviewViewer(path, action.previewViewer)
15
+ ? normalizeWorkspaceFilePreviewViewer(
16
+ path,
17
+ action.previewViewer ?? (action.line ? 'source' : undefined),
18
+ )
16
19
  : action.previewViewer ?? null;
17
20
  const viewIdentity = action.viewMode === 'preview' && previewViewer === 'rendered'
18
21
  ? 'preview:rendered'
@@ -1,6 +1,7 @@
1
1
  import type { ResolvedChildSessionTab } from "@/features/chat/features/ncp/hooks/use-ncp-child-session-tabs-view";
2
2
  import { shouldShowUnreadSessionIndicator } from "@/features/chat/stores/chat-session-list.store";
3
3
  import type {
4
+ ChatWorkspaceNavigationEntry,
4
5
  ChatWorkspacePanelKind,
5
6
  ChatWorkspaceFileTab,
6
7
  ChatWorkspaceSideChatDraft,
@@ -11,6 +12,7 @@ import {
11
12
  resolveWorkspaceFileViewer,
12
13
  type ChatWorkspaceFileViewer,
13
14
  } from "@/features/chat/features/workspace/utils/chat-workspace-file-viewer.utils";
15
+ import { areWorkspaceNavigationEntriesEqual } from "@/features/chat/features/workspace/utils/chat-thread-workspace-session.utils";
14
16
 
15
17
  export type WorkspaceSelection =
16
18
  | {
@@ -151,41 +153,54 @@ export function resolveWorkspaceSelection(params: {
151
153
  return null;
152
154
  }
153
155
 
154
- export function buildWorkspaceTabsViewModel(params: {
156
+ type WorkspaceTabsViewModelParams = {
155
157
  resolvedChildTabs: ResolvedChildSessionTab[];
156
158
  activeSideChatDraft: ChatWorkspaceSideChatDraft | null;
159
+ closedWorkspaceTabEntries: readonly ChatWorkspaceNavigationEntry[];
157
160
  workspaceFileTabs: readonly ChatWorkspaceFileTab[];
158
161
  activeSelection: WorkspaceSelection | null;
159
162
  optimisticReadAtBySessionKey: Record<string, string>;
160
163
  onSelectSession: (sessionKey: string) => void;
161
164
  onSelectFile: (fileKey: string) => void;
162
165
  onOpenFileViewer: (fileKey: string, viewer: ChatWorkspaceFileViewer) => void;
163
- onCloseFile: (fileKey: string) => void;
166
+ onCloseTab: (entry: ChatWorkspaceNavigationEntry) => void;
164
167
  onSelectOverview: () => void;
165
168
  onSelectChildSessions: () => void;
166
169
  onSelectProjectFiles: () => void;
167
170
  onSelectCronJobs: () => void;
168
- }): WorkspaceTabViewModel[] {
169
- const {
170
- activeSideChatDraft,
171
- resolvedChildTabs,
172
- workspaceFileTabs,
173
- activeSelection,
174
- optimisticReadAtBySessionKey,
175
- onSelectSession,
176
- onSelectFile,
177
- onOpenFileViewer,
178
- onCloseFile,
179
- onSelectOverview,
180
- onSelectChildSessions,
181
- onSelectProjectFiles,
182
- onSelectCronJobs,
183
- } = params;
171
+ };
184
172
 
185
- const workspacePages = [
173
+ function createWorkspacePageTabEntry(
174
+ kind: ChatWorkspacePanelKind,
175
+ ): ChatWorkspaceNavigationEntry | null {
176
+ if (kind === "child-sessions" || kind === "cron" || kind === "project-files") {
177
+ return { kind };
178
+ }
179
+ return null;
180
+ }
181
+
182
+ function buildWorkspacePageTabs({
183
+ activeSelection,
184
+ closedWorkspaceTabEntries,
185
+ onCloseTab,
186
+ onSelectChildSessions,
187
+ onSelectCronJobs,
188
+ onSelectOverview,
189
+ onSelectProjectFiles,
190
+ }: Pick<
191
+ WorkspaceTabsViewModelParams,
192
+ | "activeSelection"
193
+ | "closedWorkspaceTabEntries"
194
+ | "onCloseTab"
195
+ | "onSelectChildSessions"
196
+ | "onSelectCronJobs"
197
+ | "onSelectOverview"
198
+ | "onSelectProjectFiles"
199
+ >): WorkspaceTabViewModel[] {
200
+ const tabs: WorkspaceTabViewModel[] = [
186
201
  {
187
202
  key: "overview",
188
- kind: "overview" as const,
203
+ kind: "overview",
189
204
  title: t("chatWorkspaceOverview"),
190
205
  tooltip: t("chatWorkspaceOverview"),
191
206
  active: activeSelection?.kind === "overview",
@@ -193,29 +208,69 @@ export function buildWorkspaceTabsViewModel(params: {
193
208
  },
194
209
  {
195
210
  key: "child-sessions",
196
- kind: "child-sessions" as const,
211
+ kind: "child-sessions",
197
212
  title: t("chatWorkspaceChildSessions"),
198
213
  tooltip: t("chatWorkspaceChildSessions"),
199
214
  active: activeSelection?.kind === "child-sessions",
200
215
  onSelect: onSelectChildSessions,
216
+ onClose: () => onCloseTab({ kind: "child-sessions" }),
201
217
  },
202
218
  {
203
219
  key: "cron:session",
204
- kind: "cron" as const,
220
+ kind: "cron",
205
221
  title: t("chatWorkspaceSessionCronJobs"),
206
222
  tooltip: t("chatWorkspaceSessionCronJobs"),
207
223
  active: activeSelection?.kind === "cron",
208
224
  onSelect: onSelectCronJobs,
225
+ onClose: () => onCloseTab({ kind: "cron" }),
209
226
  },
210
227
  {
211
228
  key: "project-files",
212
- kind: "project-files" as const,
229
+ kind: "project-files",
213
230
  title: t("chatWorkspaceProjectFiles"),
214
231
  tooltip: t("chatWorkspaceProjectFiles"),
215
232
  active: activeSelection?.kind === "project-files",
216
233
  onSelect: onSelectProjectFiles,
234
+ onClose: () => onCloseTab({ kind: "project-files" }),
217
235
  },
218
236
  ];
237
+ return tabs.filter((tab) => {
238
+ const entry = createWorkspacePageTabEntry(tab.kind);
239
+ return !entry || !closedWorkspaceTabEntries.some((candidate) =>
240
+ areWorkspaceNavigationEntriesEqual(candidate, entry),
241
+ );
242
+ });
243
+ }
244
+
245
+ export function buildWorkspaceTabsViewModel(
246
+ params: WorkspaceTabsViewModelParams,
247
+ ): WorkspaceTabViewModel[] {
248
+ const {
249
+ activeSideChatDraft,
250
+ closedWorkspaceTabEntries,
251
+ resolvedChildTabs,
252
+ workspaceFileTabs,
253
+ activeSelection,
254
+ optimisticReadAtBySessionKey,
255
+ onSelectSession,
256
+ onSelectFile,
257
+ onOpenFileViewer,
258
+ onCloseTab,
259
+ onSelectOverview,
260
+ onSelectChildSessions,
261
+ onSelectProjectFiles,
262
+ onSelectCronJobs,
263
+ } = params;
264
+
265
+ const workspacePages = buildWorkspacePageTabs({
266
+ activeSelection,
267
+ closedWorkspaceTabEntries,
268
+ onCloseTab,
269
+ onSelectChildSessions,
270
+ onSelectCronJobs,
271
+ onSelectOverview,
272
+ onSelectProjectFiles,
273
+ });
219
274
 
220
275
  const sideChatDraftTabs = activeSideChatDraft
221
276
  ? [
@@ -228,11 +283,23 @@ export function buildWorkspaceTabsViewModel(params: {
228
283
  activeSelection?.kind === "side-chat-draft" &&
229
284
  activeSelection.draft.draftKey === activeSideChatDraft.draftKey,
230
285
  onSelect: () => undefined,
286
+ onClose: () => onCloseTab({
287
+ kind: "side-chat-draft",
288
+ key: activeSideChatDraft.draftKey,
289
+ }),
231
290
  },
232
291
  ]
233
292
  : [];
234
293
 
235
- const childTabs = resolvedChildTabs.map((tab) => {
294
+ const childTabs = resolvedChildTabs.filter(
295
+ (tab) =>
296
+ !closedWorkspaceTabEntries.some((entry) =>
297
+ areWorkspaceNavigationEntriesEqual(entry, {
298
+ kind: "child-session",
299
+ key: tab.sessionKey,
300
+ }),
301
+ ),
302
+ ).map((tab) => {
236
303
  const optimisticReadAt = optimisticReadAtBySessionKey[tab.sessionKey];
237
304
  const effectiveReadAt =
238
305
  optimisticReadAt && tab.readAt
@@ -259,6 +326,10 @@ export function buildWorkspaceTabsViewModel(params: {
259
326
  runStatus: tab.runStatus,
260
327
  }),
261
328
  onSelect: () => onSelectSession(tab.sessionKey),
329
+ onClose: () => onCloseTab({
330
+ kind: "child-session",
331
+ key: tab.sessionKey,
332
+ }),
262
333
  };
263
334
  });
264
335
 
@@ -292,7 +363,7 @@ export function buildWorkspaceTabsViewModel(params: {
292
363
  active:
293
364
  activeSelection?.kind === "file" && activeSelection.file.key === file.key,
294
365
  onSelect: () => onSelectFile(file.key),
295
- onClose: () => onCloseFile(file.key),
366
+ onClose: () => onCloseTab({ kind: "file", key: file.key }),
296
367
  };
297
368
  });
298
369