@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
@@ -19,6 +19,7 @@ const captures = vi.hoisted(() => ({
19
19
  openFilePreview: vi.fn(),
20
20
  handleToolAction: vi.fn(),
21
21
  showContent: vi.fn(),
22
+ filePreviewProps: [] as Array<{ showBreadcrumbs?: boolean }>,
22
23
  }));
23
24
 
24
25
  vi.mock("@nextclaw/agent-chat-ui", async (importOriginal) => {
@@ -61,12 +62,25 @@ vi.mock("@/shared/lib/i18n", () => ({
61
62
  t: (key: string) => key,
62
63
  }));
63
64
 
65
+ vi.mock(
66
+ "@/features/chat/features/workspace/components/chat-session-workspace-file-preview",
67
+ () => ({
68
+ ChatSessionWorkspaceFilePreview: (props: {
69
+ showBreadcrumbs?: boolean;
70
+ }) => {
71
+ captures.filePreviewProps.push(props);
72
+ return <div data-testid="inline-workspace-file-preview" />;
73
+ },
74
+ }),
75
+ );
76
+
64
77
  beforeEach(() => {
65
78
  captures.renders = [];
66
79
  captures.language = "en";
67
80
  captures.openFilePreview.mockReset();
68
81
  captures.handleToolAction.mockReset();
69
82
  captures.showContent.mockReset();
83
+ captures.filePreviewProps = [];
70
84
  });
71
85
 
72
86
  it("reuses adapted message references when the source message object is unchanged", () => {
@@ -484,22 +498,40 @@ it("passes the inline panel app renderer to the shared chat UI", () => {
484
498
  expect(captures.renders[captures.renders.length - 1]?.renderPanelAppCard).toEqual(expect.any(Function));
485
499
  });
486
500
 
487
- it("passes the inline display renderer to the shared chat UI", () => {
501
+ it("renders inline file displays through the workspace file preview", () => {
488
502
  render(<ChatMessageListContainer messages={[]} isSending={false} />);
489
503
 
490
504
  const renderInlineDisplay =
491
505
  captures.renders[captures.renders.length - 1]?.renderInlineDisplay;
492
506
 
493
507
  expect(renderInlineDisplay).toEqual(expect.any(Function));
494
- expect(
495
- renderInlineDisplay?.({
496
- target: {
497
- type: "file",
498
- payload: { path: "README.md" },
499
- },
500
- title: "README",
501
- }),
502
- ).toBeUndefined();
508
+ const rendered = renderInlineDisplay?.({
509
+ target: {
510
+ type: "file",
511
+ payload: { path: "preview.html", viewer: "rendered" },
512
+ },
513
+ title: "Preview",
514
+ description: "Rendered HTML",
515
+ });
516
+
517
+ if (!isValidElement<{ display?: unknown }>(rendered)) {
518
+ throw new Error("Expected inline file renderer to return a React element");
519
+ }
520
+ expect(rendered.props.display).toEqual({
521
+ target: {
522
+ type: "file",
523
+ payload: { path: "preview.html", viewer: "rendered" },
524
+ },
525
+ title: "Preview",
526
+ description: "Rendered HTML",
527
+ });
528
+
529
+ render(rendered);
530
+
531
+ expect(screen.getByText("Preview")).toBeTruthy();
532
+ expect(screen.getByText("preview.html")).toBeTruthy();
533
+ expect(screen.queryByText("Rendered HTML")).toBeNull();
534
+ expect(captures.filePreviewProps.at(-1)?.showBreadcrumbs).toBe(false);
503
535
  });
504
536
 
505
537
  it("keeps inline panel app displays expandable from the card header", () => {
@@ -0,0 +1,80 @@
1
+ import type {
2
+ ChatFileOpenActionViewModel,
3
+ ChatInlineDisplayViewModel,
4
+ } from "@nextclaw/agent-chat-ui";
5
+ import { FileText } from "lucide-react";
6
+ import { ChatSessionWorkspaceFilePreview } from "@/features/chat/features/workspace/components/chat-session-workspace-file-preview";
7
+ import { createWorkspaceFileTab } from "@/features/chat/features/workspace/utils/chat-workspace-file-tab.utils";
8
+
9
+ type ChatInlineFilePreviewProps = {
10
+ display: ChatInlineDisplayViewModel;
11
+ parentSessionKey: string | null;
12
+ sessionProjectRoot: string | null;
13
+ sessionWorkingDir: string | null;
14
+ onFileOpen: (action: ChatFileOpenActionViewModel) => void;
15
+ };
16
+
17
+ function readFileName(path: string): string {
18
+ return path.split(/[\\/]/).filter(Boolean).pop() ?? path;
19
+ }
20
+
21
+ export function ChatInlineFilePreview({
22
+ display,
23
+ parentSessionKey,
24
+ sessionProjectRoot,
25
+ sessionWorkingDir,
26
+ onFileOpen,
27
+ }: ChatInlineFilePreviewProps) {
28
+ if (display.target.type !== "file") {
29
+ return null;
30
+ }
31
+ const { path, line, column, viewer } = display.target.payload;
32
+ const file = createWorkspaceFileTab(
33
+ {
34
+ path,
35
+ label: display.title,
36
+ viewMode: "preview",
37
+ previewViewer: viewer,
38
+ line,
39
+ column,
40
+ },
41
+ parentSessionKey,
42
+ );
43
+ if (!file) {
44
+ return null;
45
+ }
46
+ const title = display.title ?? readFileName(file.path);
47
+
48
+ return (
49
+ <section
50
+ className="my-2 w-full max-w-[48rem] overflow-hidden rounded-lg border border-border bg-card shadow-sm"
51
+ data-chat-inline-file-preview="true"
52
+ data-chat-message-wide-content="true"
53
+ >
54
+ <header className="border-b border-border bg-muted/45 px-3 py-2">
55
+ <div className="flex min-w-0 items-center gap-2 text-xs font-medium text-foreground">
56
+ <FileText
57
+ className="h-3.5 w-3.5 shrink-0 text-muted-foreground"
58
+ aria-hidden
59
+ />
60
+ <span className="truncate">{title}</span>
61
+ </div>
62
+ <p
63
+ className="mt-1 truncate font-mono text-[11px] font-normal text-muted-foreground"
64
+ title={file.path}
65
+ >
66
+ {file.path}
67
+ </p>
68
+ </header>
69
+ <div className="h-[420px] min-h-[240px] max-h-[min(60vh,420px)] overflow-hidden">
70
+ <ChatSessionWorkspaceFilePreview
71
+ file={file}
72
+ sessionProjectRoot={sessionProjectRoot}
73
+ sessionWorkingDir={sessionWorkingDir}
74
+ showBreadcrumbs={false}
75
+ onFileOpen={onFileOpen}
76
+ />
77
+ </div>
78
+ </section>
79
+ );
80
+ }
@@ -4,13 +4,11 @@ import {
4
4
  type ChatInlineDisplayViewModel,
5
5
  type ChatInlineTokenViewModel,
6
6
  type ChatMessageViewModel,
7
+ type ChatPanelAppCardViewModel,
7
8
  ChatMessageList,
8
9
  } from "@nextclaw/agent-chat-ui";
9
10
  import { usePresenter } from "@/features/chat/components/providers/chat-presenter.provider";
10
- import {
11
- adaptChatMessage,
12
- type ChatMessageSource,
13
- } from "@/features/chat/features/message/utils/chat-message.utils";
11
+ import { adaptChatMessage, type ChatMessageSource } from "@/features/chat/features/message/utils/chat-message.utils";
14
12
  import { buildChatMessageProcessSummary } from "@/features/chat/features/message/utils/chat-message-process-summary.utils";
15
13
  import {
16
14
  buildChatMessageAdapterTexts,
@@ -23,6 +21,7 @@ import {
23
21
  type ContextCompactionTimelineView,
24
22
  } from "@/features/chat/features/session/utils/ncp-session-context-metadata.utils";
25
23
  import { AgentIdentityAvatar } from "@/shared/components/common/agent-identity";
24
+ import { ChatInlineFilePreview } from "@/features/chat/features/message/components/chat-inline-file-preview";
26
25
  import { ChatInlinePanelAppCard } from "@/features/chat/features/message/components/chat-inline-panel-app-card";
27
26
  import { useChatQueryStore } from "@/features/chat/stores/ncp-chat-query.store";
28
27
  import { useChatSessionListStore } from "@/features/chat/stores/chat-session-list.store";
@@ -62,6 +61,10 @@ function renderChatInlineDisplay(display: ChatInlineDisplayViewModel) {
62
61
  />
63
62
  );
64
63
  }
64
+
65
+ const renderChatToolAgent = (agentId: string) => <AgentIdentityAvatar agentId={agentId} className="h-4 w-4 shrink-0" />;
66
+ const renderChatPanelAppCard = (panelApp: ChatPanelAppCardViewModel) => <ChatInlinePanelAppCard panelApp={panelApp} />;
67
+
65
68
  type ContextInheritanceTimelineView = {
66
69
  sourceSessionId: string;
67
70
  inheritedMessageCount: number;
@@ -88,11 +91,7 @@ type ChatTimelineItem =
88
91
  inheritance: ContextInheritanceTimelineView;
89
92
  };
90
93
 
91
- function ChatContextCompactionDivider({
92
- checkpoint,
93
- }: {
94
- checkpoint: ContextCompactionTimelineView;
95
- }) {
94
+ function ChatContextCompactionDivider({ checkpoint }: { checkpoint: ContextCompactionTimelineView }) {
96
95
  const title = [
97
96
  `${t("chatContextCompactionCoveredMessages")}: ${checkpoint.coveredSessionMessageCount}`,
98
97
  `${t("chatContextCompactionOriginalTokens")}: ${checkpoint.originalEstimatedTokens}`,
@@ -118,11 +117,7 @@ function ChatContextCompactionDivider({
118
117
  );
119
118
  }
120
119
 
121
- function ChatContextInheritanceDivider({
122
- inheritance,
123
- }: {
124
- inheritance: ContextInheritanceTimelineView;
125
- }) {
120
+ function ChatContextInheritanceDivider({ inheritance }: { inheritance: ContextInheritanceTimelineView }) {
126
121
  const title = [
127
122
  `${t("chatContextInheritanceSourceSession")}: ${inheritance.sourceSessionId}`,
128
123
  `${t("chatContextInheritanceMessages")}: ${inheritance.inheritedMessageCount}`,
@@ -144,20 +139,12 @@ function resolveCompactionBoundaryIndex(params: {
144
139
  visibleRawMessages: readonly NcpMessage[];
145
140
  rawMessageId: string;
146
141
  }): number {
147
- const {
148
- rawMessageId,
149
- rawMessages,
150
- visibleRawMessages,
151
- } = params;
152
- const physicalIndex = rawMessages.findIndex(
153
- (message) => message.id === rawMessageId,
154
- );
142
+ const { rawMessageId, rawMessages, visibleRawMessages } = params;
143
+ const physicalIndex = rawMessages.findIndex((message) => message.id === rawMessageId);
155
144
  if (physicalIndex < 0) {
156
145
  return visibleRawMessages.length - 1;
157
146
  }
158
- const previousVisibleCount = rawMessages
159
- .slice(0, physicalIndex)
160
- .filter(isVisibleChatMessage).length;
147
+ const previousVisibleCount = rawMessages.slice(0, physicalIndex).filter(isVisibleChatMessage).length;
161
148
  return previousVisibleCount - 1;
162
149
  }
163
150
 
@@ -170,9 +157,7 @@ function isVisibleChatMessage(message: NcpMessage): boolean {
170
157
  return !readContextCompactionTimeline(message) && !readInheritedSourceSessionId(message);
171
158
  }
172
159
 
173
- function resolveContextInheritanceBoundary(
174
- messages: readonly NcpMessage[],
175
- ): ContextInheritanceTimelineBoundary | null {
160
+ function resolveContextInheritanceBoundary(messages: readonly NcpMessage[]): ContextInheritanceTimelineBoundary | null {
176
161
  const boundaryIndex = messages.findIndex((message) => readInheritedSourceSessionId(message));
177
162
  if (boundaryIndex < 0) {
178
163
  return null;
@@ -184,7 +169,8 @@ function resolveContextInheritanceBoundary(
184
169
  return {
185
170
  boundaryIndex: messages.slice(0, boundaryIndex).filter(isVisibleChatMessage).length,
186
171
  sourceSessionId,
187
- inheritedMessageCount: messages.filter((message) => readInheritedSourceSessionId(message) === sourceSessionId).length,
172
+ inheritedMessageCount: messages.filter((message) => readInheritedSourceSessionId(message) === sourceSessionId)
173
+ .length,
188
174
  };
189
175
  }
190
176
 
@@ -199,8 +185,12 @@ function buildTimelineItems(params: {
199
185
  checkpoint: readContextCompactionTimeline(message),
200
186
  }))
201
187
  .filter(
202
- (entry): entry is { rawMessageId: string; checkpoint: ContextCompactionTimelineView } =>
203
- Boolean(entry.checkpoint),
188
+ (
189
+ entry,
190
+ ): entry is {
191
+ rawMessageId: string;
192
+ checkpoint: ContextCompactionTimelineView;
193
+ } => Boolean(entry.checkpoint),
204
194
  )
205
195
  .map((entry) => ({
206
196
  key: entry.rawMessageId,
@@ -290,19 +280,35 @@ export function ChatMessageListContainer({
290
280
  const presenter = usePresenter();
291
281
  const { language } = useI18n();
292
282
  const messageLayout = useChatMessageLayoutStore((state) => state.layout);
293
- const selectedSessionKey = useChatSessionListStore(
294
- (state) => state.snapshot.selectedSessionKey,
295
- );
283
+ const selectedSessionKey = useChatSessionListStore((state) => state.snapshot.selectedSessionKey);
296
284
  const selectedSession = useNcpChatSelectedSession(selectedSessionKey);
297
285
  const localFileBasePath = selectedSession?.workingDir ?? selectedSession?.projectRoot ?? null;
286
+ const renderInlineDisplayWithFiles = useCallback(
287
+ (display: ChatInlineDisplayViewModel) => {
288
+ const panelAppDisplay = renderChatInlineDisplay(display);
289
+ if (panelAppDisplay) {
290
+ return panelAppDisplay;
291
+ }
292
+ if (display.target.type === "file") {
293
+ return (
294
+ <ChatInlineFilePreview
295
+ display={display}
296
+ parentSessionKey={selectedSessionKey}
297
+ sessionProjectRoot={selectedSession?.projectRoot ?? null}
298
+ sessionWorkingDir={localFileBasePath}
299
+ onFileOpen={presenter.chatThreadManager.openFilePreview}
300
+ />
301
+ );
302
+ }
303
+ return undefined;
304
+ },
305
+ [localFileBasePath, presenter.chatThreadManager, selectedSession?.projectRoot, selectedSessionKey],
306
+ );
298
307
  const resolveFileContentUrl = useCallback(
299
308
  (action: { path: string }) => buildServerPathContentUrl(action.path, localFileBasePath),
300
309
  [localFileBasePath],
301
310
  );
302
- const texts = useMemo(
303
- () => buildChatMessageAdapterTexts(language),
304
- [language],
305
- );
311
+ const texts = useMemo(() => buildChatMessageAdapterTexts(language), [language]);
306
312
 
307
313
  const messages = useMemo(() => {
308
314
  const visibleRawMessages = rawMessages.filter(isVisibleChatMessage);
@@ -335,7 +341,11 @@ export function ChatMessageListContainer({
335
341
  texts,
336
342
  });
337
343
 
338
- messageViewModelCache.set(message, { language, processSummaryLabel, viewModel });
344
+ messageViewModelCache.set(message, {
345
+ language,
346
+ processSummaryLabel,
347
+ viewModel,
348
+ });
339
349
  return [viewModel];
340
350
  });
341
351
  }, [language, rawMessages, texts]);
@@ -343,20 +353,12 @@ export function ChatMessageListContainer({
343
353
  const hasAssistantDraft = useMemo(
344
354
  () =>
345
355
  messages.some(
346
- (message) =>
347
- message.role === "assistant" &&
348
- (message.status === "streaming" || message.status === "pending"),
356
+ (message) => message.role === "assistant" && (message.status === "streaming" || message.status === "pending"),
349
357
  ),
350
358
  [messages],
351
359
  );
352
- const messageTexts = useMemo(
353
- () => buildChatMessageTexts(language),
354
- [language],
355
- );
356
- const timelineItems = useMemo(
357
- () => buildTimelineItems({ rawMessages, messages }),
358
- [messages, rawMessages],
359
- );
360
+ const messageTexts = useMemo(() => buildChatMessageTexts(language), [language]);
361
+ const timelineItems = useMemo(() => buildTimelineItems({ rawMessages, messages }), [messages, rawMessages]);
360
362
  const sessionSkillsQuery = useChatQueryStore((state) => state.snapshot.sessionSkillsQuery);
361
363
  const handleInlineTokenClick = useCallback(
362
364
  (token: ChatInlineTokenViewModel) => {
@@ -419,16 +421,9 @@ export function ChatMessageListContainer({
419
421
  onAttachmentOpen={handleAttachmentOpen}
420
422
  onInlineTokenClick={handleInlineTokenClick}
421
423
  resolveFileContentUrl={resolveFileContentUrl}
422
- renderInlineDisplay={renderChatInlineDisplay}
423
- renderToolAgent={(agentId) => (
424
- <AgentIdentityAvatar
425
- agentId={agentId}
426
- className="h-4 w-4 shrink-0"
427
- />
428
- )}
429
- renderPanelAppCard={(panelApp) => (
430
- <ChatInlinePanelAppCard panelApp={panelApp} />
431
- )}
424
+ renderInlineDisplay={renderInlineDisplayWithFiles}
425
+ renderToolAgent={renderChatToolAgent}
426
+ renderPanelAppCard={renderChatPanelAppCard}
432
427
  />
433
428
  ),
434
429
  )}
@@ -106,6 +106,8 @@ export function buildChatMessageTexts(
106
106
  copiedCodeLabel: t("chatCodeCopied"),
107
107
  copyMessageLabel: t("chatMessageCopy"),
108
108
  copiedMessageLabel: t("chatMessageCopied"),
109
+ mermaidDiagramLabel: t("chatMermaidDiagram"),
110
+ mermaidRenderErrorLabel: t("chatMermaidRenderError"),
109
111
  typingLabel: t("chatTyping"),
110
112
  reasoningCharacterCountTemplates: {
111
113
  inProgress: t("chatReasoningInProgressCharacterCount"),
@@ -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>