@nextclaw/ui 0.17.1 → 0.17.2

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 (229) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/dist/assets/{abnfDiagram-VRR7QNED-BcFJp_1X.js → abnfDiagram-VRR7QNED-DvTjgVMf.js} +1 -1
  3. package/dist/assets/{aiden0z-pptx-renderer.es-Dp2ezlQn.js → aiden0z-pptx-renderer.es-C1nfBAXR.js} +1 -1
  4. package/dist/assets/{api-DJ6ZKWFY.js → api-CuPWhNDM.js} +3 -3
  5. package/dist/assets/{appearance-settings-page-B-juoJ6F.js → appearance-settings-page-DRjg6UP3.js} +1 -1
  6. package/dist/assets/{arc-CkgyPMyW.js → arc-BvhODBXv.js} +1 -1
  7. package/dist/assets/architecture-TIHT7OUA-Bjp9jf0h.js +1 -0
  8. package/dist/assets/{architectureDiagram-ZJ3FMSHR-CMd4cIjI.js → architectureDiagram-ZJ3FMSHR-BKO9RgNS.js} +1 -1
  9. package/dist/assets/{blockDiagram-677ZJIJ3-Bz3tbhet.js → blockDiagram-677ZJIJ3-DHpDXy7D.js} +1 -1
  10. package/dist/assets/book-open-B4ovWX14.js +1 -0
  11. package/dist/assets/{c4Diagram-LMCZKHZV-D9hFNAGv.js → c4Diagram-LMCZKHZV-BG_iQnDr.js} +1 -1
  12. package/dist/assets/channel-5T3pdR-s.js +1 -0
  13. package/dist/assets/{channels-list-page-mVHYa7xW.js → channels-list-page-B26GXq63.js} +2 -2
  14. package/dist/assets/{chat-page-DsMYGNee.js → chat-page-QA0dpNyU.js} +1 -1
  15. package/dist/assets/chunk-2Q5K7J3B-DTZ3tGfE.js +1 -0
  16. package/dist/assets/{chunk-32BRIVSS-7NrjEITq.js → chunk-32BRIVSS-DR9M0LLx.js} +1 -1
  17. package/dist/assets/{chunk-52WLFC77-viIcqj9q.js → chunk-52WLFC77-698iC3BB.js} +1 -1
  18. package/dist/assets/{chunk-5FCAYU7R-Vsu4J-VM.js → chunk-5FCAYU7R-DikTj9Ey.js} +1 -1
  19. package/dist/assets/{chunk-5HE753X5-C-w6CQZK.js → chunk-5HE753X5-ko0o7_q0.js} +1 -1
  20. package/dist/assets/{chunk-5JV3BV7I-CuKgJQ4W.js → chunk-5JV3BV7I-CEHufhgo.js} +1 -1
  21. package/dist/assets/{chunk-5TONJI2A-CYeiWZKb.js → chunk-5TONJI2A-CGgno1cu.js} +1 -1
  22. package/dist/assets/{chunk-5VM5RSS4-DRCbfwIn.js → chunk-5VM5RSS4-DkiB4cUQ.js} +1 -1
  23. package/dist/assets/{chunk-7BUUIJ7U-BvydOcm3.js → chunk-7BUUIJ7U-DzptcytQ.js} +1 -1
  24. package/dist/assets/{chunk-BIQX33UG-RlppOXKo.js → chunk-BIQX33UG-DMRSQJvT.js} +1 -1
  25. package/dist/assets/{chunk-C7G6YPKG-DAizzYwr.js → chunk-C7G6YPKG-Bw1BwwuY.js} +1 -1
  26. package/dist/assets/{chunk-CQNSW5MT-5eChJHca.js → chunk-CQNSW5MT-C0z6eHBk.js} +1 -1
  27. package/dist/assets/{chunk-CYSBUYHQ-YHPr53fv.js → chunk-CYSBUYHQ-ByPyAouO.js} +1 -1
  28. package/dist/assets/{chunk-EMLP6XTP-CK4D7Hz5.js → chunk-EMLP6XTP-CJlAXWZg.js} +1 -1
  29. package/dist/assets/{chunk-EX3LRPZG-BmlDFIRW.js → chunk-EX3LRPZG-Dqq4zKJu.js} +1 -1
  30. package/dist/assets/{chunk-FWX5IMBZ-D0ksHZZ9.js → chunk-FWX5IMBZ-B0SNoCmi.js} +2 -2
  31. package/dist/assets/{chunk-HOUHSVGY-DPkq8FCm.js → chunk-HOUHSVGY-DG_GJih_.js} +1 -1
  32. package/dist/assets/{chunk-ICXQ74PX-FAatv5fC.js → chunk-ICXQ74PX-Drxk_6bE.js} +1 -1
  33. package/dist/assets/{chunk-JG7HCLWE-DLkIi4F7.js → chunk-JG7HCLWE-DdLFPpnd.js} +1 -1
  34. package/dist/assets/{chunk-JWPE2WC7-DawOjNE3.js → chunk-JWPE2WC7-B8M4G76F.js} +1 -1
  35. package/dist/assets/{chunk-MOJQB5TN-Di52TorW.js → chunk-MOJQB5TN-XP6FJHY_.js} +1 -1
  36. package/dist/assets/{chunk-MOZMSUNE-PfyK9w1-.js → chunk-MOZMSUNE-C_6x18Qw.js} +1 -1
  37. package/dist/assets/{chunk-OGEWGWER-C3jdH3Ty.js → chunk-OGEWGWER-N25BzCmG.js} +1 -1
  38. package/dist/assets/{chunk-OSBZ3O6U-CecHo-f0.js → chunk-OSBZ3O6U-ASDEm7y0.js} +1 -1
  39. package/dist/assets/{chunk-PUDLZKDR-f3IJh3sN.js → chunk-PUDLZKDR-CTlvAcn7.js} +1 -1
  40. package/dist/assets/{chunk-Q4XR5HBZ-DQFeUPxZ.js → chunk-Q4XR5HBZ-Da9BWfB6.js} +1 -1
  41. package/dist/assets/{chunk-QBLGF6JB-BhUeG0y2.js → chunk-QBLGF6JB-Bi6RpTJO.js} +1 -1
  42. package/dist/assets/{chunk-R7FJI6CG-C3XpZ-2u.js → chunk-R7FJI6CG-DkMsSPvK.js} +1 -1
  43. package/dist/assets/{chunk-RYQCIY6F-CAqa5klQ.js → chunk-RYQCIY6F-DZp0eU-j.js} +1 -1
  44. package/dist/assets/{chunk-U6XO7XAA-D_WXezeD.js → chunk-U6XO7XAA-vfinDSrj.js} +1 -1
  45. package/dist/assets/{chunk-V7JOEXUC-CZ70oOtp.js → chunk-V7JOEXUC-C9Nksccs.js} +1 -1
  46. package/dist/assets/chunk-VAUOI2AC-HLGIHQ6k.js +1 -0
  47. package/dist/assets/{chunk-VR4S4FIN-B7AHEYYk.js → chunk-VR4S4FIN-Xv6zfwXs.js} +1 -1
  48. package/dist/assets/{chunk-WYO6CB5R-Dn9To_OI.js → chunk-WYO6CB5R-mOJNzbWl.js} +2 -2
  49. package/dist/assets/chunk-XXDRQBXY-Dl0m6Ni-.js +1 -0
  50. package/dist/assets/{chunk-YOTPTUD7-BYEgRBk3.js → chunk-YOTPTUD7-1-aKSu4o.js} +1 -1
  51. package/dist/assets/{chunk-ZGVPDNZ5-C6YtZDU0.js → chunk-ZGVPDNZ5-OJ8veMpK.js} +1 -1
  52. package/dist/assets/{chunk-ZIRB5QZD-DMuYtnHF.js → chunk-ZIRB5QZD-SR_Nq_PP.js} +1 -1
  53. package/dist/assets/classDiagram-OUVF2IWQ-DcDnzGNX.js +1 -0
  54. package/dist/assets/classDiagram-v2-EOCWNBFH-BKXKMzKM.js +1 -0
  55. package/dist/assets/{config-split-page-BaqLcRxk.js → config-split-page-Box_-aBK.js} +1 -1
  56. package/dist/assets/{confirm-dialog-DasFoaCT.js → confirm-dialog-uqcCxomz.js} +1 -1
  57. package/dist/assets/{cose-bilkent-JH36ORCC-Bc2Ex5Y3.js → cose-bilkent-JH36ORCC-NjVgz2Bw.js} +1 -1
  58. package/dist/assets/cynefin-VYW2F7L2-BWg7U2UD.js +1 -0
  59. package/dist/assets/{cynefinDiagram-TSTJHNR4-DVysQoGz.js → cynefinDiagram-TSTJHNR4-B_17LDqD.js} +1 -1
  60. package/dist/assets/{dagre-CMhQ1cAG.js → dagre-CMe101gk.js} +1 -1
  61. package/dist/assets/{dagre-VKFMJZFB-RYyWpPJZ.js → dagre-VKFMJZFB-qo8nLb3B.js} +1 -1
  62. package/dist/assets/{desktop-update-config-CoEvKrpB.js → desktop-update-config-DkDor159.js} +1 -1
  63. package/dist/assets/{diagram-FQU43EPY-BD6KONdF.js → diagram-FQU43EPY-Ba3XIklC.js} +1 -1
  64. package/dist/assets/{diagram-G47NLZAW-CGB2ptRR.js → diagram-G47NLZAW-CG45TcUx.js} +1 -1
  65. package/dist/assets/{diagram-NH7WQ7WH-DnkOI6cU.js → diagram-NH7WQ7WH-2F2HrwV-.js} +1 -1
  66. package/dist/assets/{diagram-OA4YK3LP-DF82RZ7P.js → diagram-OA4YK3LP-CXfWH9NZ.js} +1 -1
  67. package/dist/assets/{diagram-WEI45ONY-Be2v7Ck9.js → diagram-WEI45ONY-CH_4yepc.js} +1 -1
  68. package/dist/assets/{dist-C741xUwt.js → dist-CZLIZntJ.js} +1 -1
  69. package/dist/assets/{dist-zOKNc7QQ.js → dist-Co2G-YjN.js} +1 -1
  70. package/dist/assets/doc-browser-CbCBDdap.js +1 -0
  71. package/dist/assets/doc-browser-DUqckL2E.js +1 -0
  72. package/dist/assets/{doc-browser-context-BT8NWQyM.js → doc-browser-context-aeZu0FHR.js} +1 -1
  73. package/dist/assets/{docx-preview-BqwAjrj3.js → docx-preview-CoM4JK7_.js} +1 -1
  74. package/dist/assets/{ebnfDiagram-CCIWWBDH-CP98OYk8.js → ebnfDiagram-CCIWWBDH-BwiG4Ea1.js} +1 -1
  75. package/dist/assets/{erDiagram-Q63AITRT-ZLlx8xfs.js → erDiagram-Q63AITRT-BML3hpnQ.js} +1 -1
  76. package/dist/assets/eventmodeling-45OFAUF4-SB-J4tpq.js +1 -0
  77. package/dist/assets/flowDiagram-23GEKE2U-BQ_u-eLM.js +1 -0
  78. package/dist/assets/{form-actions-BcukaMyR.js → form-actions-C3Pa1HQb.js} +1 -1
  79. package/dist/assets/{ganttDiagram-NO4QXBWP-DXCDjHR2.js → ganttDiagram-NO4QXBWP-80enfWbD.js} +1 -1
  80. package/dist/assets/gitGraph-TEB2WS4Q-DPxNwsFd.js +1 -0
  81. package/dist/assets/{gitGraphDiagram-IHSO6WYX-l2HWrTJp.js → gitGraphDiagram-IHSO6WYX-CVLqXr-b.js} +1 -1
  82. package/dist/assets/index-Bsoscjk7.css +1 -0
  83. package/dist/assets/index-CCWgeWoR.js +109 -0
  84. package/dist/assets/info-DKCQHKI2-DNlNGH90.js +1 -0
  85. package/dist/assets/{infoDiagram-FWYZ7A6U-D-DjX-bn.js → infoDiagram-FWYZ7A6U-Dyvs2t0i.js} +1 -1
  86. package/dist/assets/{ishikawaDiagram-FXEZZL3T-y_Xy128F.js → ishikawaDiagram-FXEZZL3T-sSnxDKfh.js} +1 -1
  87. package/dist/assets/{journeyDiagram-5HDEW3XC-DJn5xYcQ.js → journeyDiagram-5HDEW3XC-D22V71Mf.js} +1 -1
  88. package/dist/assets/{kanban-definition-HUTT4EX6-DJVIOR21.js → kanban-definition-HUTT4EX6-D3ds0p-g.js} +1 -1
  89. package/dist/assets/{line-Cos7PIOj.js → line-CbyFs065.js} +1 -1
  90. package/dist/assets/{linear-BtDLqtzF.js → linear-4DKtnZBS.js} +1 -1
  91. package/dist/assets/loader-circle-DfbWUPdI.js +1 -0
  92. package/dist/assets/{map-BKnQyvd0.js → map-CcZzEoz6.js} +1 -1
  93. package/dist/assets/mcp-marketplace-page-B2D7ciJL.js +1 -0
  94. package/dist/assets/mcp-marketplace-page-BmqbIQQQ.js +9 -0
  95. package/dist/assets/{mermaid-parser.core-XUNZhr2W.js → mermaid-parser.core-DPmMOjXD.js} +2 -2
  96. package/dist/assets/{mermaid.core-ySrvolIe.js → mermaid.core-BSf-ddRa.js} +3 -3
  97. package/dist/assets/{mindmap-definition-LN4V7U3C-BypL9tei.js → mindmap-definition-LN4V7U3C-BUTwWPyx.js} +1 -1
  98. package/dist/assets/model-config-page-BXUoZEEZ.js +1 -0
  99. package/dist/assets/{navigation-link-BoMNAKyw.js → navigation-link-DngetDmN.js} +1 -1
  100. package/dist/assets/{ordinal-CxxVWxYU.js → ordinal-BgLYtOZE.js} +1 -1
  101. package/dist/assets/packet-7NZHBO7P-BpeiqkBZ.js +1 -0
  102. package/dist/assets/{pegDiagram-2B236MQR-D-U2KBGv.js → pegDiagram-2B236MQR-BEu_ZBjc.js} +1 -1
  103. package/dist/assets/pie-RZYD4A2V-Cy1yxsPd.js +1 -0
  104. package/dist/assets/{pieDiagram-ENE6RG2P-CBi3GusJ.js → pieDiagram-ENE6RG2P-CAwAwuBd.js} +1 -1
  105. package/dist/assets/plus-wNpOxBfP.js +1 -0
  106. package/dist/assets/{provider-scoped-model-input-CB-vojwC.js → provider-scoped-model-input-C4a2TuV7.js} +1 -1
  107. package/dist/assets/{providers-config-page-C80nti5H.js → providers-config-page-CHSka1kQ.js} +1 -1
  108. package/dist/assets/{quadrantDiagram-ABIIQ3AL-H0ZuvnZL.js → quadrantDiagram-ABIIQ3AL-CwE6F-_2.js} +1 -1
  109. package/dist/assets/radar-I7S5WNFK-CEcPKCj8.js +1 -0
  110. package/dist/assets/railroad-3IZDKUUU-CW38eNDs.js +1 -0
  111. package/dist/assets/railroad-abnf-AHOZXSZD-ByrylcXf.js +1 -0
  112. package/dist/assets/railroad-ebnf-EBAXGLYW-CNB6GwP-.js +1 -0
  113. package/dist/assets/railroad-peg-LSFZ7HO6-x6hnqlBG.js +1 -0
  114. package/dist/assets/{railroadDiagram-RFXS5EU6-D7Y2EqSd.js → railroadDiagram-RFXS5EU6-xgLekU3B.js} +1 -1
  115. package/dist/assets/react-CPH0zySO.js +8 -0
  116. package/dist/assets/{refresh-cw-DZ_Y89ld.js → refresh-cw-vTYZqC0b.js} +1 -1
  117. package/dist/assets/remote-DgfRmokf.js +1 -0
  118. package/dist/assets/{requirementDiagram-TGXJPOKE-BiXNX1NN.js → requirementDiagram-TGXJPOKE-CyVUzEX5.js} +1 -1
  119. package/dist/assets/rotate-cw-L_seGUwq.js +1 -0
  120. package/dist/assets/runtime-config-page-V75_zqXa.js +1 -0
  121. package/dist/assets/{sankeyDiagram-HTMAVEWB-qzDeLztW.js → sankeyDiagram-HTMAVEWB-JK6bFmYB.js} +1 -1
  122. package/dist/assets/{save-BgMfxs5u.js → save-lWb6TI69.js} +1 -1
  123. package/dist/assets/{search-C7ruoV7u.js → search-BuUX_0KY.js} +1 -1
  124. package/dist/assets/{search-config-page-BTjfOZ6q.js → search-config-page-rlkRJZCF.js} +1 -1
  125. package/dist/assets/{secrets-config-page-BFjiSz73.js → secrets-config-page-BxCEc8r5.js} +2 -2
  126. package/dist/assets/security-config-Cqxe13za.js +1 -0
  127. package/dist/assets/{select-BMX2ooE-.js → select-oalsltZV.js} +1 -1
  128. package/dist/assets/{sequenceDiagram-DBY2YBRQ-BtRbPQtL.js → sequenceDiagram-DBY2YBRQ-BR5IokXV.js} +1 -1
  129. package/dist/assets/{settings-2-BNQF6hed.js → settings-2-Bd9xiNAY.js} +1 -1
  130. package/dist/assets/{sizeCapture-X5ZJPWSS-CMYvM3SL.js → sizeCapture-X5ZJPWSS-SxG_YnMA.js} +1 -1
  131. package/dist/assets/skeleton-D72EdBNa.js +1 -0
  132. package/dist/assets/{src-Bw-mzbQb.js → src-vgz8E_-O.js} +1 -1
  133. package/dist/assets/{stateDiagram-2N3HPSRC-BJ-mFeOz.js → stateDiagram-2N3HPSRC-DD4OoYhn.js} +1 -1
  134. package/dist/assets/stateDiagram-v2-6OUMAXLB-ZC6dFORv.js +1 -0
  135. package/dist/assets/{swimlanes-5IMT3BWC-BsucVOO2.js → swimlanes-5IMT3BWC-BWkFeyCT.js} +2 -2
  136. package/dist/assets/swimlanesDiagram-G3AALYLV-CRkh51Im.js +8 -0
  137. package/dist/assets/{tag-chip-BL-UXlVO.js → tag-chip-B324d_Xk.js} +1 -1
  138. package/dist/assets/{timeline-definition-FHXFAJF6-Bicewlzo.js → timeline-definition-FHXFAJF6-VTRPy6KA.js} +1 -1
  139. package/dist/assets/treeView-QDETBFTQ-Bw0DSCH7.js +1 -0
  140. package/dist/assets/treemap-6X3UGDF4-DFWBFcfh.js +1 -0
  141. package/dist/assets/{vennDiagram-L72KCM5P-BxOOnO9x.js → vennDiagram-L72KCM5P-Bvk42Pek.js} +1 -1
  142. package/dist/assets/wardley-OPB4EBWU-CoWturk0.js +1 -0
  143. package/dist/assets/{wardleyDiagram-EHGQE667-Cl7NltMv.js → wardleyDiagram-EHGQE667-DOFjx2s3.js} +1 -1
  144. package/dist/assets/x-Cps-je1K.js +1 -0
  145. package/dist/assets/{xychartDiagram-FW5EYKEG-DiMIxQ7N.js → xychartDiagram-FW5EYKEG-DY5yRTUc.js} +1 -1
  146. package/dist/index.html +22 -21
  147. package/package.json +9 -9
  148. package/src/features/chat/features/message/utils/__tests__/chat-message-show-content-tool-card.utils.test.ts +46 -0
  149. package/src/features/chat/features/message/utils/chat-message-part.utils.ts +2 -0
  150. package/src/features/chat/features/message/utils/chat-message-tool-card.utils.ts +52 -2
  151. package/src/features/chat/features/ncp/hooks/__tests__/use-hydrated-ncp-agent.test.tsx +97 -10
  152. package/src/features/chat/features/ncp/hooks/use-ncp-session-conversation.ts +4 -0
  153. package/src/features/chat/features/ncp/hooks/use-ncp-session-queries.ts +10 -0
  154. package/src/features/chat/features/session/utils/__tests__/ncp-session-adapter.utils.test.ts +27 -0
  155. package/src/features/chat/features/session/utils/ncp-session-adapter.utils.ts +2 -1
  156. package/src/features/chat/features/workspace/components/__tests__/chat-session-child-sessions.test.tsx +51 -0
  157. package/src/features/chat/features/workspace/components/__tests__/chat-session-token-usage.test.tsx +90 -0
  158. package/src/features/chat/features/workspace/components/__tests__/chat-session-workspace-panel-content.test.tsx +9 -17
  159. package/src/features/chat/features/workspace/components/chat-session-workspace-panel-content.tsx +4 -45
  160. package/src/features/chat/features/workspace/components/child-sessions/chat-session-child-sessions.tsx +66 -0
  161. package/src/features/chat/features/workspace/components/overview/chat-session-token-usage.tsx +171 -0
  162. package/src/features/chat/types/chat-message.types.ts +5 -0
  163. package/src/shared/components/doc-browser/__tests__/doc-browser-tab-strip.test.tsx +78 -0
  164. package/src/shared/components/doc-browser/doc-browser-tab-strip.tsx +71 -3
  165. package/src/shared/components/ui/tab-strip/__tests__/compact-tab-strip.test.tsx +78 -1
  166. package/src/shared/components/ui/tab-strip/compact-tab-strip.tsx +97 -44
  167. package/src/shared/lib/api/__tests__/ncp-session-query-cache.utils.test.ts +2 -0
  168. package/src/shared/lib/api/ncp-session-query-cache.utils.ts +4 -0
  169. package/src/shared/lib/api/ncp-session.types.ts +3 -0
  170. package/src/shared/lib/api/types.ts +1 -0
  171. package/src/shared/lib/api/utils/ncp-session.utils.ts +8 -0
  172. package/src/shared/lib/i18n/locales/en-US/chat.json +19 -0
  173. package/src/shared/lib/i18n/locales/zh-CN/chat.json +20 -1
  174. package/dist/assets/architecture-TIHT7OUA-DO2x53FK.js +0 -1
  175. package/dist/assets/channel-nBvMaVyP.js +0 -1
  176. package/dist/assets/chunk-2Q5K7J3B-CJgqFDS4.js +0 -1
  177. package/dist/assets/chunk-VAUOI2AC-BGvh3yIx.js +0 -1
  178. package/dist/assets/chunk-XXDRQBXY-DaBxG5gs.js +0 -1
  179. package/dist/assets/classDiagram-OUVF2IWQ-P6N03G2R.js +0 -1
  180. package/dist/assets/classDiagram-v2-EOCWNBFH-B1_uLvMv.js +0 -1
  181. package/dist/assets/cynefin-VYW2F7L2-B-H8Qp-n.js +0 -1
  182. package/dist/assets/doc-browser-BbirIYbj.js +0 -1
  183. package/dist/assets/doc-browser-Crqog5cn.js +0 -1
  184. package/dist/assets/eventmodeling-45OFAUF4-YVtShyFA.js +0 -1
  185. package/dist/assets/flowDiagram-23GEKE2U-BeKXabVO.js +0 -1
  186. package/dist/assets/gitGraph-TEB2WS4Q-jTSiBj-p.js +0 -1
  187. package/dist/assets/index-1f4Pv2q3.css +0 -1
  188. package/dist/assets/index-BYWLjYFQ.js +0 -109
  189. package/dist/assets/info-DKCQHKI2-BIporNWb.js +0 -1
  190. package/dist/assets/loader-circle-LVHz84Xj.js +0 -1
  191. package/dist/assets/mcp-marketplace-page-2SsrbaiQ.js +0 -1
  192. package/dist/assets/mcp-marketplace-page-XgYLOAkK.js +0 -9
  193. package/dist/assets/model-config-page-DL8ljJK-.js +0 -1
  194. package/dist/assets/packet-7NZHBO7P-CuFMADV0.js +0 -1
  195. package/dist/assets/pie-RZYD4A2V-XUeIXhM9.js +0 -1
  196. package/dist/assets/plus-BeGZHC3f.js +0 -1
  197. package/dist/assets/radar-I7S5WNFK-D-NNGh1k.js +0 -1
  198. package/dist/assets/railroad-3IZDKUUU-C67_uDaM.js +0 -1
  199. package/dist/assets/railroad-abnf-AHOZXSZD-BZ8Yflnd.js +0 -1
  200. package/dist/assets/railroad-ebnf-EBAXGLYW-B7zO7RoK.js +0 -1
  201. package/dist/assets/railroad-peg-LSFZ7HO6-R63PTgLz.js +0 -1
  202. package/dist/assets/react-DND3vEAM.js +0 -8
  203. package/dist/assets/remote-Codkr9SX.js +0 -1
  204. package/dist/assets/rotate-cw-CP3pQ4R8.js +0 -1
  205. package/dist/assets/runtime-config-page-Um1XDqXJ.js +0 -1
  206. package/dist/assets/security-config-Dx-wMb4q.js +0 -1
  207. package/dist/assets/skeleton-CWZ0IO2D.js +0 -1
  208. package/dist/assets/stateDiagram-v2-6OUMAXLB-BE-HqxOK.js +0 -1
  209. package/dist/assets/swimlanesDiagram-G3AALYLV-Dr1H9LJH.js +0 -8
  210. package/dist/assets/treeView-QDETBFTQ-npJ5dbqS.js +0 -1
  211. package/dist/assets/treemap-6X3UGDF4-pYrBx6oa.js +0 -1
  212. package/dist/assets/wardley-OPB4EBWU-CmSRKLR8.js +0 -1
  213. package/dist/assets/x-BSu1xwPK.js +0 -1
  214. /package/dist/assets/{array-BQVzeGRt.js → array-NADKd9aU.js} +0 -0
  215. /package/dist/assets/{chunk-KEIR6QF5-f9psyCXK.js → chunk-KEIR6QF5-BuyuzJJL.js} +0 -0
  216. /package/dist/assets/{chunk-Y2CYZVJY-D1mAU_Po.js → chunk-Y2CYZVJY-D_B2ZRNt.js} +0 -0
  217. /package/dist/assets/{config-hints-nV5HPOvq.js → config-hints-BcfVUMij.js} +0 -0
  218. /package/dist/assets/{cytoscape.esm-0uDtdD0l.js → cytoscape.esm-BANTlgza.js} +0 -0
  219. /package/dist/assets/{defaultLocale-DSWXGgt0.js → defaultLocale-C4_L0aEs.js} +0 -0
  220. /package/dist/assets/{dist--ta5rX12.js → dist-DAPEIJbz.js} +0 -0
  221. /package/dist/assets/{graphlib-CQ6QV4yP.js → graphlib-ChqXg0dI.js} +0 -0
  222. /package/dist/assets/{host-capabilities-Tk038i04.js → host-capabilities-D9C4CBK5.js} +0 -0
  223. /package/dist/assets/{init-Y6h9jGtZ.js → init-D8wZg9xq.js} +0 -0
  224. /package/dist/assets/{jszip.min-8hyJpgpx.js → jszip.min-CkbBfwAQ.js} +0 -0
  225. /package/dist/assets/{katex-ARhY9PS1.js → katex-TZJFa0J_.js} +0 -0
  226. /package/dist/assets/{middleware-DXsbf9xI.js → middleware-B3k-TSmZ.js} +0 -0
  227. /package/dist/assets/{path-10BjdyLh.js → path-DodfxcX7.js} +0 -0
  228. /package/dist/assets/{rough.esm-DxgC_14r.js → rough.esm-CUaa1Rbe.js} +0 -0
  229. /package/dist/assets/{xlsx-DrDuDNAa.js → xlsx-CxByi7bR.js} +0 -0
@@ -0,0 +1,171 @@
1
+ import { ChartNoAxesColumn } from 'lucide-react';
2
+
3
+ import { useNcpSessionTokenUsage } from '@/features/chat/features/ncp/hooks/use-ncp-session-queries';
4
+ import { t } from '@/shared/lib/i18n';
5
+ import { cn } from '@/shared/lib/utils';
6
+
7
+ function formatTokenCount(value: number | null): string {
8
+ return value === null ? '—' : new Intl.NumberFormat().format(value);
9
+ }
10
+
11
+ function formatCacheHitRate(value: number | null): string {
12
+ return value === null
13
+ ? '—'
14
+ : new Intl.NumberFormat(undefined, {
15
+ style: 'percent',
16
+ maximumFractionDigits: 1,
17
+ }).format(value);
18
+ }
19
+
20
+ function TokenUsageStatusBadge({ status }: { status: 'reported' | 'partial' | 'unavailable' }) {
21
+ return (
22
+ <span
23
+ className={cn(
24
+ 'inline-flex shrink-0 items-center rounded-full px-2 py-0.5 text-[10px] font-semibold',
25
+ status === 'reported'
26
+ ? 'bg-emerald-50 text-emerald-700'
27
+ : status === 'partial'
28
+ ? 'bg-amber-50 text-amber-700'
29
+ : 'bg-gray-100 text-gray-500',
30
+ )}
31
+ >
32
+ {t(
33
+ status === 'reported'
34
+ ? 'chatWorkspaceTokenUsageReported'
35
+ : status === 'partial'
36
+ ? 'chatWorkspaceTokenUsagePartial'
37
+ : 'chatWorkspaceTokenUsageUnavailable',
38
+ )}
39
+ </span>
40
+ );
41
+ }
42
+
43
+ function TokenUsageMetric({ label, value }: { label: string; value: string }) {
44
+ return (
45
+ <div className="min-w-0 rounded-md bg-gray-50 px-2.5 py-2">
46
+ <div className="truncate text-[10px] font-medium text-gray-500">{label}</div>
47
+ <div className="mt-0.5 truncate text-sm font-semibold tabular-nums text-gray-900">
48
+ {value}
49
+ </div>
50
+ </div>
51
+ );
52
+ }
53
+
54
+ export function ChatSessionTokenUsage({ sessionKey }: { sessionKey: string | null }) {
55
+ const usageQuery = useNcpSessionTokenUsage(sessionKey);
56
+ const usage = usageQuery.data;
57
+
58
+ return (
59
+ <section
60
+ aria-labelledby="chat-workspace-token-usage-title"
61
+ className="mt-4 rounded-lg border border-gray-200/80 bg-white p-3"
62
+ >
63
+ <div className="flex items-start gap-3">
64
+ <span className="inline-flex h-9 w-9 shrink-0 items-center justify-center rounded-lg bg-gray-100 text-gray-600">
65
+ <ChartNoAxesColumn className="h-4 w-4" />
66
+ </span>
67
+ <div className="min-w-0 flex-1">
68
+ <div className="flex items-center justify-between gap-2">
69
+ <h3 id="chat-workspace-token-usage-title" className="text-sm font-semibold text-gray-900">
70
+ {t('chatWorkspaceTokenUsage')}
71
+ </h3>
72
+ {usage ? <TokenUsageStatusBadge status={usage.status} /> : null}
73
+ </div>
74
+ <p className="mt-0.5 text-xs leading-5 text-gray-500">
75
+ {t('chatWorkspaceTokenUsageDescription')}
76
+ </p>
77
+ </div>
78
+ </div>
79
+
80
+ {usageQuery.isLoading ? (
81
+ <p className="py-5 text-center text-xs text-gray-500" aria-live="polite">
82
+ {t('chatWorkspaceTokenUsageLoading')}
83
+ </p>
84
+ ) : usageQuery.isError ? (
85
+ <div className="flex items-center justify-between gap-3 py-4 text-xs text-rose-600" role="alert">
86
+ <span>{t('chatWorkspaceTokenUsageLoadFailed')}</span>
87
+ <button
88
+ type="button"
89
+ className="shrink-0 rounded px-1.5 py-1 font-semibold text-gray-700 hover:bg-gray-100 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/25"
90
+ onClick={() => void usageQuery.refetch()}
91
+ >
92
+ {t('chatWorkspaceTokenUsageRetry')}
93
+ </button>
94
+ </div>
95
+ ) : usage && usage.runCount > 0 ? (
96
+ <div className="mt-3 space-y-3">
97
+ <div className="grid grid-cols-2 gap-1.5">
98
+ <TokenUsageMetric
99
+ label={t('chatWorkspaceTokenUsageTotal')}
100
+ value={formatTokenCount(usage.totals.totalTokens)}
101
+ />
102
+ <TokenUsageMetric
103
+ label={t('chatWorkspaceTokenUsageInput')}
104
+ value={formatTokenCount(usage.totals.inputTokens)}
105
+ />
106
+ <TokenUsageMetric
107
+ label={t('chatWorkspaceTokenUsageOutput')}
108
+ value={formatTokenCount(usage.totals.outputTokens)}
109
+ />
110
+ <TokenUsageMetric
111
+ label={t('chatWorkspaceTokenUsageCachedInput')}
112
+ value={formatTokenCount(usage.totals.cachedInputTokens)}
113
+ />
114
+ <TokenUsageMetric
115
+ label={t('chatWorkspaceTokenUsageCacheHitRate')}
116
+ value={formatCacheHitRate(usage.totals.cacheHitRate)}
117
+ />
118
+ <TokenUsageMetric
119
+ label={t('chatWorkspaceTokenUsageRuns')}
120
+ value={String(usage.runCount)}
121
+ />
122
+ </div>
123
+ <div>
124
+ <div className="mb-1.5 text-[11px] font-medium text-gray-700">
125
+ {t('chatWorkspaceTokenUsageByModel')}
126
+ </div>
127
+ <div className="space-y-1.5">
128
+ {usage.models.map((modelUsage) => (
129
+ <div key={modelUsage.model} className="rounded-md border border-gray-200/70 px-2.5 py-2">
130
+ <div className="flex min-w-0 items-center justify-between gap-2">
131
+ <span className="min-w-0 truncate text-xs font-semibold text-gray-900" title={modelUsage.model}>
132
+ {modelUsage.model}
133
+ </span>
134
+ <TokenUsageStatusBadge status={modelUsage.status} />
135
+ </div>
136
+ <div className="mt-1 text-[10px] text-gray-500">
137
+ {t('chatWorkspaceTokenUsageModelRunCount').replace('{count}', String(modelUsage.runCount))}
138
+ {' · '}
139
+ {t('chatWorkspaceTokenUsageModelCacheHitRate').replace(
140
+ '{rate}',
141
+ formatCacheHitRate(modelUsage.cacheHitRate),
142
+ )}
143
+ </div>
144
+ <dl className="mt-2 grid grid-cols-2 gap-x-3 gap-y-1 text-[10px] sm:grid-cols-4">
145
+ {[
146
+ [t('chatWorkspaceTokenUsageInput'), modelUsage.inputTokens],
147
+ [t('chatWorkspaceTokenUsageOutput'), modelUsage.outputTokens],
148
+ [t('chatWorkspaceTokenUsageCachedInput'), modelUsage.cachedInputTokens],
149
+ [t('chatWorkspaceTokenUsageTotal'), modelUsage.totalTokens],
150
+ ].map(([label, value]) => (
151
+ <div key={String(label)} className="flex min-w-0 items-center justify-between gap-1 sm:block">
152
+ <dt className="truncate text-gray-500">{label}</dt>
153
+ <dd className="shrink-0 font-semibold tabular-nums text-gray-800 sm:mt-0.5">
154
+ {formatTokenCount(value as number | null)}
155
+ </dd>
156
+ </div>
157
+ ))}
158
+ </dl>
159
+ </div>
160
+ ))}
161
+ </div>
162
+ </div>
163
+ </div>
164
+ ) : (
165
+ <p className="py-5 text-center text-xs leading-5 text-gray-500">
166
+ {t('chatWorkspaceTokenUsageEmpty')}
167
+ </p>
168
+ )}
169
+ </section>
170
+ );
171
+ }
@@ -55,6 +55,11 @@ export type ChatMessagePartSource =
55
55
  error?: string;
56
56
  cancelled?: boolean;
57
57
  toolCallId?: string;
58
+ execution?: {
59
+ startedAt?: string;
60
+ endedAt?: string;
61
+ durationMs?: number;
62
+ };
58
63
  };
59
64
  }
60
65
  | {
@@ -0,0 +1,78 @@
1
+ import { fireEvent, render, screen } from "@testing-library/react";
2
+ import { describe, expect, it, vi } from "vitest";
3
+ import { DocBrowserTabStrip } from "@/shared/components/doc-browser/doc-browser-tab-strip";
4
+ import type { DocBrowserTab } from "@/shared/components/doc-browser/doc-browser-context";
5
+
6
+ const docsTab: DocBrowserTab = {
7
+ id: "docs",
8
+ kind: "docs",
9
+ title: "Docs",
10
+ currentUrl: "nextclaw://docs",
11
+ history: ["nextclaw://docs"],
12
+ historyIndex: 0,
13
+ navVersion: 0,
14
+ };
15
+
16
+ function renderTabStrip(tabs: DocBrowserTab[]) {
17
+ return render(
18
+ <DocBrowserTabStrip
19
+ tabs={tabs}
20
+ activeTabId={tabs[0]?.id ?? ""}
21
+ canGoBack={false}
22
+ canGoForward={false}
23
+ isDocked={true}
24
+ isFullscreen={false}
25
+ onGoBack={vi.fn()}
26
+ onGoForward={vi.fn()}
27
+ onOpenNewTab={vi.fn()}
28
+ onSetActiveTab={vi.fn()}
29
+ onCloseTab={vi.fn()}
30
+ onClose={vi.fn()}
31
+ onDragStart={vi.fn()}
32
+ onToggleMode={vi.fn()}
33
+ />,
34
+ );
35
+ }
36
+
37
+ describe("DocBrowserTabStrip", () => {
38
+ it("keeps content-sized tabs and renders each tab's own icon", () => {
39
+ renderTabStrip([
40
+ docsTab,
41
+ {
42
+ ...docsTab,
43
+ id: "marketplace",
44
+ kind: "content",
45
+ title: "Marketplace",
46
+ currentUrl: "nextclaw://marketplace",
47
+ dockIcon: { type: "url", url: "/marketplace-icon.png" },
48
+ },
49
+ ]);
50
+
51
+ expect(
52
+ screen.getByRole("button", { name: "Docs" })
53
+ .querySelector(".lucide-book-open"),
54
+ ).toBeTruthy();
55
+ expect(
56
+ screen.getByRole("button", { name: "Marketplace" })
57
+ .querySelector('img[src="/marketplace-icon.png"]'),
58
+ ).toBeTruthy();
59
+
60
+ const marketplaceTab = screen.getByRole("button", { name: "Marketplace" })
61
+ .closest("[data-compact-tab-item]");
62
+ expect(marketplaceTab?.className).toContain("max-w-[220px]");
63
+ expect(marketplaceTab?.className).not.toContain("min-w-[");
64
+
65
+ fireEvent.error(
66
+ screen.getByRole("button", { name: "Marketplace" })
67
+ .querySelector('img[src="/marketplace-icon.png"]')!,
68
+ );
69
+ expect(
70
+ screen.getByRole("button", { name: "Marketplace" })
71
+ .querySelector(".lucide-app-window"),
72
+ ).toBeTruthy();
73
+ expect(
74
+ screen.getByRole("button", { name: "Marketplace" })
75
+ .querySelector("img"),
76
+ ).toBeNull();
77
+ });
78
+ });
@@ -1,13 +1,18 @@
1
- import type { PointerEvent } from "react";
1
+ import { useState, type PointerEvent } from "react";
2
2
  import {
3
+ AppWindow,
3
4
  ArrowLeft,
4
5
  ArrowRight,
6
+ BookOpen,
7
+ Boxes,
8
+ Github,
5
9
  PanelRightOpen,
6
10
  PictureInPicture2,
7
11
  Pin,
8
12
  PinOff,
9
13
  Plus,
10
14
  X,
15
+ type LucideIcon,
11
16
  } from "lucide-react";
12
17
  import type { DocBrowserDockState, DocBrowserTab } from "./doc-browser-context";
13
18
  import type { ContextMenuGroup } from "@/shared/components/ui/context-menu/context-menu";
@@ -39,6 +44,68 @@ type DocBrowserTabStripProps = {
39
44
  getTabMenuGroups?: (tab: DocBrowserTab) => readonly ContextMenuGroup[] | undefined;
40
45
  };
41
46
 
47
+ const DOC_BROWSER_BUILTIN_TAB_ICONS: Record<string, LucideIcon> = {
48
+ apps: Boxes,
49
+ docs: BookOpen,
50
+ github: Github,
51
+ "new-tab": Plus,
52
+ "panel-app": AppWindow,
53
+ "service-apps": AppWindow,
54
+ };
55
+
56
+ function DocBrowserUrlTabIcon({
57
+ fallbackIcon: FallbackIcon,
58
+ url,
59
+ }: {
60
+ fallbackIcon: LucideIcon;
61
+ url: string;
62
+ }) {
63
+ const [loadFailed, setLoadFailed] = useState(false);
64
+ return loadFailed ? (
65
+ <FallbackIcon className="h-3.5 w-3.5 text-muted-foreground" aria-hidden="true" />
66
+ ) : (
67
+ <img
68
+ src={url}
69
+ alt=""
70
+ className="h-3.5 w-3.5 rounded-sm object-cover"
71
+ onError={() => setLoadFailed(true)}
72
+ />
73
+ );
74
+ }
75
+
76
+ function DocBrowserTabIcon({ tab }: { tab: DocBrowserTab }) {
77
+ const { dockIcon } = tab;
78
+ const fallbackIcon = tab.kind === "docs"
79
+ ? BookOpen
80
+ : tab.kind === "home"
81
+ ? Plus
82
+ : tab.kind === "apps"
83
+ ? Boxes
84
+ : AppWindow;
85
+ const Icon = dockIcon?.type === "builtin"
86
+ ? (DOC_BROWSER_BUILTIN_TAB_ICONS[dockIcon.name] ?? fallbackIcon)
87
+ : fallbackIcon;
88
+
89
+ if (dockIcon?.type === "url") {
90
+ return (
91
+ <DocBrowserUrlTabIcon
92
+ key={dockIcon.url}
93
+ fallbackIcon={fallbackIcon}
94
+ url={dockIcon.url}
95
+ />
96
+ );
97
+ }
98
+ if (dockIcon?.type === "text") {
99
+ return (
100
+ <span className="max-w-3.5 truncate text-[10px] font-semibold leading-none" aria-hidden="true">
101
+ {dockIcon.value}
102
+ </span>
103
+ );
104
+ }
105
+
106
+ return <Icon className="h-3.5 w-3.5 text-muted-foreground" aria-hidden="true" />;
107
+ }
108
+
42
109
  function shouldBlockHeaderDrag(target: EventTarget | null): boolean {
43
110
  return target instanceof Element && Boolean(
44
111
  target.closest("button, a, input, textarea, select, [data-compact-tab-item]"),
@@ -80,8 +147,9 @@ export function DocBrowserTabStrip({
80
147
  label: tab.title || t("docBrowserTabUntitled"),
81
148
  active: tab.id === activeTabId,
82
149
  tooltip: tab.title,
150
+ leadingIcon: <DocBrowserTabIcon tab={tab} />,
83
151
  closeLabel: closeTabLabel,
84
- closePlacement: "trailing",
152
+ closePlacement: "leading-hover",
85
153
  onSelect: () => onSetActiveTab(tab.id),
86
154
  onClose: () => onCloseTab(tab.id),
87
155
  menuLabel: t("docBrowserTabMoreActions"),
@@ -132,7 +200,7 @@ export function DocBrowserTabStrip({
132
200
  tabsClassName="items-center gap-0.5"
133
201
  actionsClassName="h-full items-center gap-0.5"
134
202
  actionButtonClassName="rounded-md p-1.5 text-muted-foreground hover:bg-muted/60 hover:text-foreground disabled:opacity-50"
135
- tabBaseClassName="group inline-flex min-w-0 cursor-pointer items-center gap-0.5 h-7 px-2 rounded-md text-xs max-w-[220px] shrink-0 transition-colors"
203
+ tabBaseClassName="inline-flex cursor-pointer items-center gap-0.5 h-7 px-2 rounded-md text-xs max-w-[220px] shrink-0 transition-colors"
136
204
  activeTabClassName="bg-muted/80 text-foreground"
137
205
  inactiveTabClassName="text-muted-foreground hover:bg-muted/45 hover:text-foreground"
138
206
  labelClassName="px-0.5 text-xs font-normal"
@@ -113,7 +113,7 @@ describe("CompactTabStrip", () => {
113
113
  active: false,
114
114
  closeLabel: "Close preview",
115
115
  closePlacement: "leading-hover",
116
- leadingIcon: <span />,
116
+ leadingIcon: <span data-testid="preview-tab-icon" />,
117
117
  onClose,
118
118
  onSelect,
119
119
  },
@@ -126,6 +126,83 @@ describe("CompactTabStrip", () => {
126
126
 
127
127
  expect(onClose).toHaveBeenCalledTimes(1);
128
128
  expect(onSelect).not.toHaveBeenCalled();
129
+ expect(
130
+ screen.getByTestId("preview-tab-icon").parentElement?.className,
131
+ ).toContain("group-hover:opacity-0");
132
+ });
133
+
134
+ it("overlays the close action at the start without changing the tab width on hover", () => {
135
+ render(
136
+ <CompactTabStrip
137
+ tabs={[
138
+ {
139
+ key: "marketplace",
140
+ label: "NextClaw Marketplace",
141
+ active: true,
142
+ closeLabel: "Close tab",
143
+ closePlacement: "leading-hover",
144
+ menuLabel: "More tab actions",
145
+ menuGroups: [{ key: "tab", items: [{ key: "close", label: "Close", onSelect: vi.fn() }] }],
146
+ onClose: vi.fn(),
147
+ onSelect: vi.fn(),
148
+ },
149
+ ]}
150
+ actions={[]}
151
+ />,
152
+ );
153
+
154
+ const tabItem = screen.getByRole("button", { name: "NextClaw Marketplace" })
155
+ .closest("[data-compact-tab-item]");
156
+ const leadingActions = tabItem?.querySelector(
157
+ '[data-compact-tab-leading-actions=""]',
158
+ );
159
+ const trailingActions = tabItem?.querySelector(
160
+ '[data-compact-tab-trailing-actions=""]',
161
+ );
162
+ const trailingSpacer = tabItem?.querySelector(
163
+ '[data-compact-tab-trailing-spacer=""]',
164
+ );
165
+ const leadingSlot = tabItem?.querySelector(
166
+ '[data-compact-tab-leading-slot=""]',
167
+ );
168
+
169
+ expect(tabItem?.className).toContain("relative");
170
+ expect(tabItem?.className).toContain("max-w-[180px]");
171
+ expect(tabItem?.className).not.toContain("min-w-[");
172
+ expect(leadingSlot?.className).toContain("w-3.5");
173
+ expect(leadingSlot?.className).toContain("shrink-0");
174
+ expect(leadingActions?.className).toContain("absolute");
175
+ expect(leadingActions?.className).toContain("left-1");
176
+ expect(leadingActions?.className).toContain("opacity-0");
177
+ expect(leadingActions?.className).toContain("pointer-events-none");
178
+ expect(leadingActions?.className).not.toContain("bg-inherit");
179
+ expect(leadingActions?.className).toContain("group-hover:opacity-100");
180
+ expect(leadingActions?.className).toContain("group-focus-within:opacity-100");
181
+ expect(trailingActions?.className).toContain("absolute");
182
+ expect(trailingActions?.className).toContain("opacity-0");
183
+ expect(trailingActions?.className).toContain("pointer-events-none");
184
+ expect(trailingActions?.className).not.toContain("bg-inherit");
185
+ expect(trailingActions?.className).toContain("group-hover:opacity-100");
186
+ expect(trailingActions?.className).toContain("group-focus-within:opacity-100");
187
+ expect(trailingActions?.className).not.toContain("w-0");
188
+ expect(trailingActions?.className).not.toContain("transition-[width");
189
+ expect(trailingSpacer?.className).toContain("w-6");
190
+ expect(trailingSpacer?.className).toContain("shrink-0");
191
+ const closeButton = leadingActions?.querySelector<HTMLButtonElement>(
192
+ '[aria-label="Close tab"]',
193
+ );
194
+ const menuButton = trailingActions?.querySelector<HTMLButtonElement>(
195
+ '[aria-label="More tab actions"]',
196
+ );
197
+ expect(closeButton).toBeTruthy();
198
+ expect(menuButton).toBeTruthy();
199
+ expect(closeButton?.className).toBe(menuButton?.className);
200
+ expect(closeButton?.className).toContain("h-6");
201
+ expect(closeButton?.className).toContain("w-6");
202
+ expect(closeButton?.className).toContain("rounded-md");
203
+ expect(
204
+ trailingActions?.querySelector('[aria-label="Close tab"]'),
205
+ ).toBeNull();
129
206
  });
130
207
 
131
208
  it("opens a tab action menu without selecting the tab or restoring stale focus after an action", async () => {
@@ -1,6 +1,9 @@
1
1
  import * as React from "react";
2
2
  import { MoreVertical, X } from "lucide-react";
3
- import { IconActionButton } from "@/shared/components/ui/actions/icon-action-button";
3
+ import {
4
+ IconActionButton,
5
+ type IconActionButtonProps,
6
+ } from "@/shared/components/ui/actions/icon-action-button";
4
7
  import {
5
8
  ContextMenu,
6
9
  ContextMenuTrigger,
@@ -63,28 +66,94 @@ function closeCompactTab(event: React.MouseEvent<HTMLButtonElement>, onClose: ()
63
66
  onClose();
64
67
  }
65
68
 
69
+ function CompactTabActionButton(props: IconActionButtonProps) {
70
+ return <IconActionButton {...props} size="sm" tone="strong" />;
71
+ }
72
+
66
73
  function CompactTabMenu({ tab }: { tab: CompactTabStripTab }) {
67
74
  if (!tab.menuGroups?.some((group) => group.items.length > 0)) {
68
75
  return null;
69
76
  }
70
77
 
71
78
  const menuLabel = tab.menuLabel ?? tab.label;
79
+ return (
80
+ <div className="flex shrink-0">
81
+ <ContextMenuTrigger>
82
+ <CompactTabActionButton
83
+ icon={<MoreVertical className="h-3.5 w-3.5" />}
84
+ label={menuLabel}
85
+ />
86
+ </ContextMenuTrigger>
87
+ </div>
88
+ );
89
+ }
90
+
91
+ function CompactTabTrailingActions({ tab }: { tab: CompactTabStripTab }) {
92
+ const hasMenu = tab.menuGroups?.some((group) => group.items.length > 0) ?? false;
93
+ const hasTrailingClose = Boolean(
94
+ tab.onClose && tab.closePlacement !== "leading-hover",
95
+ );
96
+ const close = hasTrailingClose ? (
97
+ <CompactTabActionButton
98
+ icon={<X className="h-3.5 w-3.5" />}
99
+ label={tab.closeLabel ?? ""}
100
+ tooltip={tab.closeLabel ?? null}
101
+ onClick={(event) => closeCompactTab(event, tab.onClose!)}
102
+ />
103
+ ) : null;
104
+
105
+ if (!hasMenu && !hasTrailingClose) {
106
+ return null;
107
+ }
108
+
109
+ return (
110
+ <>
111
+ <span
112
+ aria-hidden="true"
113
+ data-compact-tab-trailing-spacer=""
114
+ className={cn(
115
+ "shrink-0",
116
+ hasMenu && hasTrailingClose
117
+ ? "w-[50px]"
118
+ : hasMenu
119
+ ? "w-6"
120
+ : "w-6",
121
+ )}
122
+ />
123
+ <div
124
+ data-compact-tab-trailing-actions=""
125
+ className={cn(
126
+ "pointer-events-none absolute inset-y-0 right-1 z-10 flex items-center gap-0.5 opacity-0",
127
+ "group-hover:pointer-events-auto group-hover:opacity-100 group-focus-within:pointer-events-auto group-focus-within:opacity-100",
128
+ "group-data-[context-menu-open]:pointer-events-auto group-data-[context-menu-open]:opacity-100",
129
+ )}
130
+ >
131
+ {hasMenu ? <CompactTabMenu tab={tab} /> : null}
132
+ {close}
133
+ </div>
134
+ </>
135
+ );
136
+ }
137
+
138
+ function CompactTabLeadingCloseOverlay({ tab }: { tab: CompactTabStripTab }) {
139
+ if (!tab.onClose || tab.closePlacement !== "leading-hover") {
140
+ return null;
141
+ }
142
+
72
143
  return (
73
144
  <div
145
+ data-compact-tab-leading-actions=""
74
146
  className={cn(
75
- "-ml-1.5 flex w-0 shrink-0 overflow-hidden opacity-0 transition-[width,margin,opacity] group-hover:ml-0 group-hover:w-5 group-hover:opacity-100 group-focus-within:ml-0 group-focus-within:w-5 group-focus-within:opacity-100",
76
- "group-data-[context-menu-open]:ml-0 group-data-[context-menu-open]:w-5 group-data-[context-menu-open]:opacity-100",
147
+ "pointer-events-none absolute inset-y-0 left-1 z-10 flex items-center opacity-0",
148
+ "group-hover:pointer-events-auto group-hover:opacity-100 group-focus-within:pointer-events-auto group-focus-within:opacity-100",
77
149
  )}
78
150
  >
79
- <ContextMenuTrigger>
80
- <button
81
- type="button"
82
- aria-label={menuLabel}
83
- className="inline-flex h-5 w-5 shrink-0 items-center justify-center rounded-md text-gray-400 transition-colors hover:bg-gray-200 hover:text-gray-700 focus-visible:bg-gray-200 focus-visible:text-gray-700 focus-visible:outline-none"
84
- >
85
- <MoreVertical className="h-3.5 w-3.5" />
86
- </button>
87
- </ContextMenuTrigger>
151
+ <CompactTabActionButton
152
+ icon={<X className="h-3.5 w-3.5" />}
153
+ label={tab.closeLabel ?? ""}
154
+ tooltip={tab.closeLabel ?? null}
155
+ onClick={(event) => closeCompactTab(event, tab.onClose!)}
156
+ />
88
157
  </div>
89
158
  );
90
159
  }
@@ -96,9 +165,9 @@ function CompactTabLabelButton({
96
165
  labelClassName?: string;
97
166
  tab: CompactTabStripTab;
98
167
  }) {
99
- const leadingIcon = tab.onClose && tab.closePlacement === "leading-hover"
100
- ? null
101
- : tab.leadingIcon;
168
+ const hasLeadingClose = Boolean(
169
+ tab.onClose && tab.closePlacement === "leading-hover",
170
+ );
102
171
  const button = (
103
172
  <button
104
173
  type="button"
@@ -106,9 +175,15 @@ function CompactTabLabelButton({
106
175
  aria-label={tab.label}
107
176
  className="flex min-w-0 flex-1 items-center gap-1.5 text-left"
108
177
  >
109
- {leadingIcon ? (
110
- <span className="inline-flex shrink-0 items-center justify-center">
111
- {leadingIcon}
178
+ {tab.leadingIcon || hasLeadingClose ? (
179
+ <span
180
+ data-compact-tab-leading-slot=""
181
+ className={cn(
182
+ "inline-flex h-3.5 w-3.5 shrink-0 items-center justify-center",
183
+ hasLeadingClose && "group-hover:opacity-0 group-focus-within:opacity-0",
184
+ )}
185
+ >
186
+ {tab.leadingIcon}
112
187
  </span>
113
188
  ) : null}
114
189
  <span className={cn("min-w-0 truncate text-[12px] font-medium", labelClassName, tab.labelClassName)}>
@@ -150,7 +225,6 @@ function CompactTabItem({
150
225
  | "labelClassName"
151
226
  | "tabBaseClassName"
152
227
  > & { itemRef?: React.Ref<HTMLDivElement>; tab: CompactTabStripTab }) {
153
- const leadingClose = tab.onClose && tab.closePlacement === "leading-hover";
154
228
  const item = (
155
229
  <div
156
230
  ref={itemRef}
@@ -158,38 +232,17 @@ function CompactTabItem({
158
232
  onClick={(event) => event.target === event.currentTarget && tab.onSelect()}
159
233
  className={cn(
160
234
  tabBaseClassName ??
161
- "group flex max-w-[180px] min-w-0 cursor-pointer items-center gap-1.5 border-r border-gray-200/70 border-b-2 px-2.5 py-2 transition-colors",
235
+ "flex max-w-[180px] cursor-pointer items-center gap-1.5 border-r border-gray-200/70 border-b-2 px-2.5 py-2 transition-colors",
236
+ "group relative",
162
237
  tab.active
163
238
  ? (activeTabClassName ?? "border-b-primary bg-white text-gray-900")
164
239
  : (inactiveTabClassName ??
165
240
  "border-b-transparent bg-gray-50/85 text-gray-500 hover:bg-gray-100"),
166
241
  )}
167
242
  >
168
- {leadingClose ? (
169
- <button
170
- type="button"
171
- onClick={(event) => closeCompactTab(event, tab.onClose!)}
172
- className="relative inline-flex h-5 w-5 shrink-0 items-center justify-center rounded-md text-gray-400 transition-colors hover:bg-gray-200 hover:text-gray-700"
173
- aria-label={tab.closeLabel}
174
- >
175
- <span className="flex items-center justify-center group-hover:hidden">
176
- {tab.leadingIcon}
177
- </span>
178
- <X className="hidden h-3.5 w-3.5 group-hover:block" />
179
- </button>
180
- ) : null}
181
243
  <CompactTabLabelButton tab={tab} labelClassName={labelClassName} />
182
- <CompactTabMenu tab={tab} />
183
- {tab.onClose && tab.closePlacement !== "leading-hover" ? (
184
- <IconActionButton
185
- size="sm"
186
- tone="strong"
187
- icon={<X className="h-3 w-3" />}
188
- label={tab.closeLabel ?? ""}
189
- tooltip={tab.closeLabel ?? null}
190
- onClick={(event) => closeCompactTab(event, tab.onClose!)}
191
- />
192
- ) : null}
244
+ <CompactTabLeadingCloseOverlay tab={tab} />
245
+ <CompactTabTrailingActions tab={tab} />
193
246
  </div>
194
247
  );
195
248
 
@@ -183,6 +183,7 @@ describe('ncp-session-query-cache', () => {
183
183
  it('applies realtime upsert/delete events to every ncp-sessions query cache entry', () => {
184
184
  const queryClient = new QueryClient();
185
185
  queryClient.setQueryData(['ncp-sessions', 200], createSessionsList());
186
+ queryClient.setQueryData(['ncp-session-token-usage', 'session-3'], { runCount: 1 });
186
187
 
187
188
  applyNcpSessionRealtimeEvent(queryClient, {
188
189
  type: 'session.run-status',
@@ -212,6 +213,7 @@ describe('ncp-session-query-cache', () => {
212
213
  expect(
213
214
  queryClient.getQueryData<NcpSessionsListView>(['ncp-sessions', 200])?.sessions.map((session) => session.sessionId)
214
215
  ).toEqual(['session-3', 'session-1', 'session-2']);
216
+ expect(queryClient.getQueryState(['ncp-session-token-usage', 'session-3'])?.isInvalidated).toBe(true);
215
217
 
216
218
  applyNcpSessionRealtimeEvent(queryClient, {
217
219
  type: 'session.summary.delete',