@nextclaw/ui 0.15.10 → 0.15.11

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 (360) hide show
  1. package/CHANGELOG.md +34 -0
  2. package/dist/assets/{abnfDiagram-VRR7QNED-BYpK0F5F.js → abnfDiagram-VRR7QNED-M4g_AyR8.js} +1 -1
  3. package/dist/assets/{aiden0z-pptx-renderer.es-D87ywFdC.js → aiden0z-pptx-renderer.es-Bd99vut2.js} +1 -1
  4. package/dist/assets/api-pbx14YAL.js +13 -0
  5. package/dist/assets/app-presenter-provider-Q6u2xBAn.js +106 -0
  6. package/dist/assets/appearance-settings-page-D9wJdf-0.js +1 -0
  7. package/dist/assets/{arc-DFELHWM2.js → arc-IO7WNWOB.js} +1 -1
  8. package/dist/assets/architecture-TIHT7OUA-Cii4JyA2.js +1 -0
  9. package/dist/assets/{architectureDiagram-ZJ3FMSHR-CvRhdovL.js → architectureDiagram-ZJ3FMSHR-Dc-lvdUh.js} +1 -1
  10. package/dist/assets/{blockDiagram-677ZJIJ3-DVYNkbR_.js → blockDiagram-677ZJIJ3-CcsoWpIY.js} +1 -1
  11. package/dist/assets/{book-open-w3160PZf.js → book-open-DZyssdCc.js} +1 -1
  12. package/dist/assets/{c4Diagram-LMCZKHZV-B1w-iwRe.js → c4Diagram-LMCZKHZV-Bsrtg6b2.js} +1 -1
  13. package/dist/assets/card-D7l-gWkY.js +1 -0
  14. package/dist/assets/channel-BSKkTPe8.js +1 -0
  15. package/dist/assets/channels-list-page-B3qW3T-B.js +8 -0
  16. package/dist/assets/chat-page-DUI_Jm6r.js +1 -0
  17. package/dist/assets/chunk-2Q5K7J3B-CDMOR1xZ.js +1 -0
  18. package/dist/assets/{chunk-32BRIVSS-kG3lg0bC.js → chunk-32BRIVSS-Bt08t8oG.js} +1 -1
  19. package/dist/assets/{chunk-52WLFC77-BLeB7iGk.js → chunk-52WLFC77-Ddx68Xzx.js} +1 -1
  20. package/dist/assets/{chunk-5FCAYU7R-BQga1Rr7.js → chunk-5FCAYU7R-B7p9_q55.js} +1 -1
  21. package/dist/assets/{chunk-5HE753X5-DU81hkcF.js → chunk-5HE753X5-D52KvdTm.js} +1 -1
  22. package/dist/assets/{chunk-5JV3BV7I-C2bAx0m7.js → chunk-5JV3BV7I-C329h8nI.js} +1 -1
  23. package/dist/assets/{chunk-5TONJI2A-BExIIgWY.js → chunk-5TONJI2A-BZyUBxWt.js} +1 -1
  24. package/dist/assets/{chunk-5VM5RSS4-C3XB05yh.js → chunk-5VM5RSS4-BUiYXZmu.js} +1 -1
  25. package/dist/assets/{chunk-7BUUIJ7U-JiyRtrGu.js → chunk-7BUUIJ7U-B7o-mn4r.js} +1 -1
  26. package/dist/assets/{chunk-BIQX33UG-BZRV20pI.js → chunk-BIQX33UG-Bey9GoRP.js} +1 -1
  27. package/dist/assets/{chunk-C7G6YPKG-AsyDSQ9j.js → chunk-C7G6YPKG-C6tosszM.js} +1 -1
  28. package/dist/assets/{chunk-CQNSW5MT-iN-fiXVA.js → chunk-CQNSW5MT-BNrdkgGQ.js} +1 -1
  29. package/dist/assets/{chunk-CYSBUYHQ-DhC_TRzI.js → chunk-CYSBUYHQ-DHODFeyB.js} +1 -1
  30. package/dist/assets/{chunk-EMLP6XTP-QFLK8R6d.js → chunk-EMLP6XTP-BcmLjrcq.js} +1 -1
  31. package/dist/assets/{chunk-EX3LRPZG-DzUwhkCz.js → chunk-EX3LRPZG-r-_eEtJI.js} +1 -1
  32. package/dist/assets/{chunk-FWX5IMBZ-DFelpZBy.js → chunk-FWX5IMBZ-DOObDWbf.js} +2 -2
  33. package/dist/assets/{chunk-HOUHSVGY-BMPwFEwN.js → chunk-HOUHSVGY-Bd0FnbWZ.js} +1 -1
  34. package/dist/assets/{chunk-ICXQ74PX-DH8NJMok.js → chunk-ICXQ74PX-ntNwAm4_.js} +1 -1
  35. package/dist/assets/{chunk-JG7HCLWE-D-95AsO4.js → chunk-JG7HCLWE-YtRpblZN.js} +1 -1
  36. package/dist/assets/{chunk-JWPE2WC7-DIaTv8kx.js → chunk-JWPE2WC7-De5-D9ta.js} +1 -1
  37. package/dist/assets/{chunk-MOJQB5TN-BPFLzWgj.js → chunk-MOJQB5TN-CdvKhyp2.js} +1 -1
  38. package/dist/assets/{chunk-MOZMSUNE-DR_fzxG0.js → chunk-MOZMSUNE-DJMMN_lu.js} +1 -1
  39. package/dist/assets/{chunk-OGEWGWER-TIcEoPPH.js → chunk-OGEWGWER-DOFWVw3O.js} +1 -1
  40. package/dist/assets/{chunk-OSBZ3O6U-BALNvSfG.js → chunk-OSBZ3O6U-DZkrIN9r.js} +1 -1
  41. package/dist/assets/{chunk-PUDLZKDR-I-EXhf_k.js → chunk-PUDLZKDR-D1wma2Yf.js} +1 -1
  42. package/dist/assets/{chunk-Q4XR5HBZ-uvIGstBS.js → chunk-Q4XR5HBZ-CjPy94Lz.js} +1 -1
  43. package/dist/assets/{chunk-QBLGF6JB-r-TjLdxa.js → chunk-QBLGF6JB-Hy1bEgS-.js} +1 -1
  44. package/dist/assets/{chunk-R7FJI6CG-N-1LDgvy.js → chunk-R7FJI6CG-CUPIHyh-.js} +1 -1
  45. package/dist/assets/{chunk-RYQCIY6F-jxNASx65.js → chunk-RYQCIY6F-D5Ua4pcv.js} +1 -1
  46. package/dist/assets/{chunk-U6XO7XAA-Cphbuc7q.js → chunk-U6XO7XAA-DaUQcqb7.js} +1 -1
  47. package/dist/assets/{chunk-V7JOEXUC-BLEuBf1s.js → chunk-V7JOEXUC-DX2zLfQe.js} +1 -1
  48. package/dist/assets/chunk-VAUOI2AC-BRvqNW76.js +1 -0
  49. package/dist/assets/{chunk-VR4S4FIN-BAUbR0ke.js → chunk-VR4S4FIN-CFA-WQik.js} +1 -1
  50. package/dist/assets/{chunk-WYO6CB5R-Do6SJrnY.js → chunk-WYO6CB5R-Bk2HeDvJ.js} +2 -2
  51. package/dist/assets/chunk-XXDRQBXY-CwX_mqJ3.js +1 -0
  52. package/dist/assets/{chunk-YOTPTUD7-CQqvMTCr.js → chunk-YOTPTUD7-DUG4FpBN.js} +1 -1
  53. package/dist/assets/{chunk-ZGVPDNZ5-B7kWWaXv.js → chunk-ZGVPDNZ5-70JdIGo0.js} +1 -1
  54. package/dist/assets/{chunk-ZIRB5QZD-DTU0cDNh.js → chunk-ZIRB5QZD-XZdgBt5L.js} +1 -1
  55. package/dist/assets/classDiagram-OUVF2IWQ-RzbGxG77.js +1 -0
  56. package/dist/assets/classDiagram-v2-EOCWNBFH-C_gWB7WT.js +1 -0
  57. package/dist/assets/config-split-page-DFEKStD7.js +1 -0
  58. package/dist/assets/confirm-dialog-7wpxfMCp.js +5 -0
  59. package/dist/assets/{cose-bilkent-JH36ORCC-B9LrtV-q.js → cose-bilkent-JH36ORCC-B-PQoGX-.js} +1 -1
  60. package/dist/assets/cynefin-VYW2F7L2-fSLIS0X3.js +1 -0
  61. package/dist/assets/{cynefinDiagram-TSTJHNR4-BfjvW4su.js → cynefinDiagram-TSTJHNR4-BoLCPEM0.js} +1 -1
  62. package/dist/assets/{dagre-DkiXZKkL.js → dagre-D7Ie4eXr.js} +1 -1
  63. package/dist/assets/{dagre-VKFMJZFB-B1pQKQSY.js → dagre-VKFMJZFB-CKRrwZA-.js} +1 -1
  64. package/dist/assets/desktop-update-config-DP6IbxBO.js +1 -0
  65. package/dist/assets/{diagram-FQU43EPY-cB4EZdaP.js → diagram-FQU43EPY-CASL1J3_.js} +1 -1
  66. package/dist/assets/{diagram-G47NLZAW-CtW8im1B.js → diagram-G47NLZAW-CvLPBIzf.js} +1 -1
  67. package/dist/assets/{diagram-NH7WQ7WH-BDCtrCCm.js → diagram-NH7WQ7WH-Ba6fGdYP.js} +1 -1
  68. package/dist/assets/{diagram-OA4YK3LP-DEMJ42Gt.js → diagram-OA4YK3LP-DkpfgEjM.js} +1 -1
  69. package/dist/assets/{diagram-WEI45ONY-CdCcxSYU.js → diagram-WEI45ONY-BP5UMVsN.js} +1 -1
  70. package/dist/assets/{dist-DEJlYi0E.js → dist-B9UN0HrS.js} +1 -1
  71. package/dist/assets/doc-browser-D7klPMHs.js +1 -0
  72. package/dist/assets/{doc-browser-context-CpvHiuQG.js → doc-browser-context-Bw9m83Jp.js} +1 -1
  73. package/dist/assets/doc-browser-wgEeLkn1.js +1 -0
  74. package/dist/assets/{docx-preview-zis3U0Fa.js → docx-preview-D0tp3a9T.js} +1 -1
  75. package/dist/assets/{ebnfDiagram-CCIWWBDH-DBZETBwE.js → ebnfDiagram-CCIWWBDH-TJKbUcmc.js} +1 -1
  76. package/dist/assets/{erDiagram-Q63AITRT-y2zDXprP.js → erDiagram-Q63AITRT-DzGPKkKH.js} +1 -1
  77. package/dist/assets/eventmodeling-45OFAUF4-XVcQGgha.js +1 -0
  78. package/dist/assets/eye-IVMB0Rrz.js +1 -0
  79. package/dist/assets/flowDiagram-23GEKE2U-BiFSctqz.js +1 -0
  80. package/dist/assets/{form-actions-5OsDRt0g.js → form-actions-DrFAgO58.js} +1 -1
  81. package/dist/assets/{ganttDiagram-NO4QXBWP-D2DP344e.js → ganttDiagram-NO4QXBWP-JhJzsGp2.js} +1 -1
  82. package/dist/assets/gitGraph-TEB2WS4Q-C014uXqS.js +1 -0
  83. package/dist/assets/{gitGraphDiagram-IHSO6WYX-D37uiwA2.js → gitGraphDiagram-IHSO6WYX-DZ1CKMj8.js} +1 -1
  84. package/dist/assets/index-D297NGOb.js +2 -0
  85. package/dist/assets/index-DqZoodZL.css +1 -0
  86. package/dist/assets/info-DKCQHKI2-CQ2qjGhU.js +1 -0
  87. package/dist/assets/{infoDiagram-FWYZ7A6U-CG-WAIRi.js → infoDiagram-FWYZ7A6U-BNXNKfRQ.js} +1 -1
  88. package/dist/assets/{ishikawaDiagram-FXEZZL3T-WeCTnW0f.js → ishikawaDiagram-FXEZZL3T-DoiEfjNw.js} +1 -1
  89. package/dist/assets/{journeyDiagram-5HDEW3XC-DUFBW9Td.js → journeyDiagram-5HDEW3XC-D6oK2HSy.js} +1 -1
  90. package/dist/assets/{kanban-definition-HUTT4EX6-B0lxgMpg.js → kanban-definition-HUTT4EX6-B2UnLOH-.js} +1 -1
  91. package/dist/assets/{line-B78frP_c.js → line-BM7PoxY5.js} +1 -1
  92. package/dist/assets/{linear-C6lVEcb2.js → linear-B3DiqMjk.js} +1 -1
  93. package/dist/assets/loader-circle-DDgFo4Kv.js +1 -0
  94. package/dist/assets/{logo-badge-TdPa95AC.js → logo-badge-DoZOVuPr.js} +1 -1
  95. package/dist/assets/{map-Bdri5UL5.js → map-BAAdHTqR.js} +1 -1
  96. package/dist/assets/mcp-marketplace-page-BtNExfMn.js +1 -0
  97. package/dist/assets/{mcp-marketplace-page-cm3ifaKu.js → mcp-marketplace-page-C151EGP-.js} +2 -2
  98. package/dist/assets/{mermaid-parser.core-Cue18vLo.js → mermaid-parser.core-BeMviefY.js} +2 -2
  99. package/dist/assets/{mermaid.core-CXwWRh5i.js → mermaid.core-D0Q77r9I.js} +3 -3
  100. package/dist/assets/{mindmap-definition-LN4V7U3C-CFxz0P0-.js → mindmap-definition-LN4V7U3C-BQ7r7bR8.js} +1 -1
  101. package/dist/assets/model-config-page-BmfQzc52.js +1 -0
  102. package/dist/assets/navigation-link-CJw6FLOj.js +1 -0
  103. package/dist/assets/{ordinal-Cbd8t4LB.js → ordinal-Bt2N4IkA.js} +1 -1
  104. package/dist/assets/packet-7NZHBO7P-6IFyydij.js +1 -0
  105. package/dist/assets/{pegDiagram-2B236MQR-B_Qt9zyT.js → pegDiagram-2B236MQR-B-C9-LGz.js} +1 -1
  106. package/dist/assets/pie-RZYD4A2V-spXQC_wr.js +1 -0
  107. package/dist/assets/{pieDiagram-ENE6RG2P-CRQdSuwS.js → pieDiagram-ENE6RG2P-CtirZLtn.js} +1 -1
  108. package/dist/assets/plus-CYxLSfLh.js +1 -0
  109. package/dist/assets/{popover-EulxhA0q.js → popover-DitLJE6k.js} +1 -1
  110. package/dist/assets/provider-scoped-model-input-CuAtduIc.js +1 -0
  111. package/dist/assets/providers-config-page-CrmuY4DC.js +1 -0
  112. package/dist/assets/{quadrantDiagram-ABIIQ3AL-D5kPpttq.js → quadrantDiagram-ABIIQ3AL-DiBWAzDU.js} +1 -1
  113. package/dist/assets/radar-I7S5WNFK-DWVA8lvY.js +1 -0
  114. package/dist/assets/railroad-3IZDKUUU-Ce51u8y1.js +1 -0
  115. package/dist/assets/railroad-abnf-AHOZXSZD-D7i7cqI_.js +1 -0
  116. package/dist/assets/railroad-ebnf-EBAXGLYW-BEg2hcPs.js +1 -0
  117. package/dist/assets/railroad-peg-LSFZ7HO6-CxROSzbi.js +1 -0
  118. package/dist/assets/{railroadDiagram-RFXS5EU6-DGh_TGlQ.js → railroadDiagram-RFXS5EU6-CP6b3PBI.js} +1 -1
  119. package/dist/assets/{react-Bb0EYsLS.js → react-CTDaaQ9V.js} +1 -1
  120. package/dist/assets/{refresh-cw-CexL11m1.js → refresh-cw-E5qTlQY6.js} +1 -1
  121. package/dist/assets/remote-YzPpOPmK.js +1 -0
  122. package/dist/assets/{requirementDiagram-TGXJPOKE-EmFph0H_.js → requirementDiagram-TGXJPOKE-D8YV0-Jz.js} +1 -1
  123. package/dist/assets/rotate-cw-BXeiDT6C.js +1 -0
  124. package/dist/assets/runtime-config-page-1fPJJvo5.js +1 -0
  125. package/dist/assets/{sankeyDiagram-HTMAVEWB-BXWB5Ugi.js → sankeyDiagram-HTMAVEWB-C2vA4ojE.js} +1 -1
  126. package/dist/assets/{save-DtccrZld.js → save-CTiZvAd2.js} +1 -1
  127. package/dist/assets/{search-BArc_WOX.js → search-CztGmpnR.js} +1 -1
  128. package/dist/assets/search-config-page-D3jVALZU.js +1 -0
  129. package/dist/assets/secrets-config-page-CxWLQCjZ.js +3 -0
  130. package/dist/assets/security-config-DEyj4WBh.js +1 -0
  131. package/dist/assets/{select-C9FB0jlt.js → select-BPVI2dQ_.js} +1 -1
  132. package/dist/assets/{sequenceDiagram-DBY2YBRQ-ClKnO7v0.js → sequenceDiagram-DBY2YBRQ-B9LBmpUo.js} +1 -1
  133. package/dist/assets/settings-2-Cd7QaBsq.js +1 -0
  134. package/dist/assets/{sizeCapture-X5ZJPWSS-DSo-t5ou.js → sizeCapture-X5ZJPWSS-9UIfya9-.js} +1 -1
  135. package/dist/assets/skeleton-CUpAXGoe.js +1 -0
  136. package/dist/assets/{src-Dad2ZbCt.js → src-DKf77Yzo.js} +1 -1
  137. package/dist/assets/{stateDiagram-2N3HPSRC-BvAalBfz.js → stateDiagram-2N3HPSRC-oTW44vds.js} +1 -1
  138. package/dist/assets/stateDiagram-v2-6OUMAXLB-Bui4RXQK.js +1 -0
  139. package/dist/assets/{swimlanes-5IMT3BWC-DdVqVa1a.js → swimlanes-5IMT3BWC-Cf7lcsay.js} +2 -2
  140. package/dist/assets/swimlanesDiagram-G3AALYLV-DTwUsCBz.js +8 -0
  141. package/dist/assets/{tabs-D_mjyOOf.js → tabs-DrUmYwEQ.js} +1 -1
  142. package/dist/assets/{tag-chip-C011rd9q.js → tag-chip-CmViAZKK.js} +1 -1
  143. package/dist/assets/{timeline-definition-FHXFAJF6-D4ar8IQY.js → timeline-definition-FHXFAJF6-DoCuWKDJ.js} +1 -1
  144. package/dist/assets/{trash-2-bDhfsO1O.js → trash-2-DMRJ63AB.js} +1 -1
  145. package/dist/assets/treeView-QDETBFTQ-D5zsTiuL.js +1 -0
  146. package/dist/assets/treemap-6X3UGDF4-CR3dxqNs.js +1 -0
  147. package/dist/assets/{use-config-BNrlfEck.js → use-config-gpXy2Iyc.js} +1 -1
  148. package/dist/assets/use-confirm-dialog-Du33bHPz.js +1 -0
  149. package/dist/assets/{use-viewport-layout-P0q6Nt_N.js → use-viewport-layout-D80O1d90.js} +1 -1
  150. package/dist/assets/{vennDiagram-L72KCM5P-CDXJ_i3p.js → vennDiagram-L72KCM5P-CpU0pzGe.js} +1 -1
  151. package/dist/assets/wardley-OPB4EBWU-LatRT_Lx.js +1 -0
  152. package/dist/assets/{wardleyDiagram-EHGQE667-D-dSuT_7.js → wardleyDiagram-EHGQE667-CkN6nn6-.js} +1 -1
  153. package/dist/assets/x-CsLMuNFe.js +1 -0
  154. package/dist/assets/{xychartDiagram-FW5EYKEG-C9JCzsZF.js → xychartDiagram-FW5EYKEG-DATpapm2.js} +1 -1
  155. package/dist/index.html +31 -32
  156. package/dist/runtime-icons/opencode.svg +16 -0
  157. package/package.json +10 -9
  158. package/public/runtime-icons/opencode.svg +16 -0
  159. package/src/app/components/layout/__tests__/settings-entry-page.test.tsx +6 -4
  160. package/src/app/components/layout/__tests__/sidebar.layout.test.tsx +24 -12
  161. package/src/app/components/layout/sidebar-items.tsx +11 -2
  162. package/src/app/components/layout/sidebar.tsx +33 -234
  163. package/src/app/configs/app-navigation.config.ts +10 -24
  164. package/src/app/index.tsx +0 -5
  165. package/src/features/channels/components/config/channel-form.tsx +26 -26
  166. package/src/features/channels/components/config/weixin-channel-auth-section.tsx +6 -7
  167. package/src/features/channels/pages/__tests__/channels-list-page.test.tsx +2 -2
  168. package/src/features/channels/pages/channels-list-page.tsx +33 -32
  169. package/src/features/chat/components/conversation/__tests__/chat-conversation-content.test.tsx +68 -7
  170. package/src/features/chat/components/conversation/__tests__/chat-conversation-header-section.test.tsx +27 -0
  171. package/src/features/chat/components/conversation/__tests__/chat-conversation-panel.test.tsx +0 -8
  172. package/src/features/chat/components/conversation/chat-conversation-content.tsx +65 -21
  173. package/src/features/chat/components/conversation/chat-conversation-header-section.tsx +36 -12
  174. package/src/features/chat/components/conversation/chat-conversation-panel.tsx +0 -7
  175. package/src/features/chat/components/layout/__tests__/chat-sidebar-toolbar.test.tsx +0 -1
  176. package/src/features/chat/components/layout/__tests__/chat-sidebar.test.tsx +17 -8
  177. package/src/features/chat/components/layout/chat-page-shell.tsx +2 -2
  178. package/src/features/chat/components/layout/chat-sidebar-toolbar.tsx +2 -23
  179. package/src/features/chat/components/layout/chat-sidebar.tsx +3 -12
  180. package/src/features/chat/features/conversation/components/__tests__/session-conversation-area.test.tsx +155 -93
  181. package/src/features/chat/features/conversation/components/session-conversation-area.tsx +126 -93
  182. package/src/features/chat/features/conversation/hooks/__tests__/use-session-conversation-input-state.test.tsx +8 -0
  183. package/src/features/chat/features/conversation/hooks/use-session-conversation-input-state.ts +27 -13
  184. package/src/features/chat/features/message/components/__tests__/chat-inline-file-preview.test.tsx +15 -9
  185. package/src/features/chat/features/message/components/__tests__/chat-inline-panel-app-card.test.tsx +127 -49
  186. package/src/features/chat/features/message/components/__tests__/chat-message-list.container.test.tsx +187 -20
  187. package/src/features/chat/features/message/components/chat-inline-content-surface.tsx +55 -0
  188. package/src/features/chat/features/message/components/chat-inline-file-preview.tsx +49 -71
  189. package/src/features/chat/features/message/components/chat-inline-panel-app-card.tsx +65 -55
  190. package/src/features/chat/features/message/components/chat-message-list.container.tsx +275 -267
  191. package/src/features/chat/features/message/hooks/__tests__/use-chat-message-virtualizer.test.tsx +163 -0
  192. package/src/features/chat/features/message/hooks/use-chat-message-virtualizer.ts +68 -0
  193. package/src/features/chat/features/message/utils/__tests__/chat-message-execution-summary.utils.test.ts +188 -0
  194. package/src/features/chat/features/message/utils/__tests__/chat-message-texts.utils.test.ts +14 -1
  195. package/src/features/chat/features/message/utils/__tests__/chat-message.utils.test.ts +33 -0
  196. package/src/features/chat/features/message/utils/chat-inline-panel-app-card.utils.ts +24 -9
  197. package/src/features/chat/features/message/utils/chat-message-execution-summary.utils.ts +178 -0
  198. package/src/features/chat/features/message/utils/chat-message-texts.utils.ts +44 -0
  199. package/src/features/chat/features/message/utils/chat-message-timeline.utils.ts +194 -0
  200. package/src/features/chat/features/message/utils/chat-message.utils.ts +5 -0
  201. package/src/features/chat/features/ncp/hooks/__tests__/use-ncp-session-conversation.test.tsx +151 -13
  202. package/src/features/chat/features/ncp/hooks/use-ncp-session-conversation.ts +44 -70
  203. package/src/features/chat/features/ncp/hooks/use-ncp-session-message-history.ts +197 -0
  204. package/src/features/chat/features/session/components/__tests__/chat-sidebar-session-item.test.tsx +21 -3
  205. package/src/features/chat/features/session/components/chat-sidebar-project-groups.tsx +7 -7
  206. package/src/features/chat/features/session/components/chat-sidebar-session-item.tsx +2 -2
  207. package/src/features/chat/features/session/components/session-context-icon.tsx +1 -1
  208. package/src/features/chat/features/session-type/components/__tests__/chat-session-type-option-item.test.tsx +5 -1
  209. package/src/features/chat/features/session-type/components/chat-session-type-option-item.tsx +1 -1
  210. package/src/features/chat/features/workspace/components/__tests__/workspace-file-content-preview.test.tsx +32 -10
  211. package/src/features/chat/features/workspace/components/workspace-file-content-preview.tsx +5 -15
  212. package/src/features/chat/features/workspace/utils/__tests__/chat-workspace-panel-view-model.utils.test.ts +62 -9
  213. package/src/features/chat/features/workspace/utils/chat-thread-workspace-session.utils.ts +6 -1
  214. package/src/features/chat/features/workspace/utils/chat-workspace-panel-view-model.utils.ts +1 -25
  215. package/src/features/chat/managers/__tests__/chat-session-list.manager.test.ts +190 -109
  216. package/src/features/chat/managers/__tests__/chat-thread-workspace.manager.test.ts +9 -10
  217. package/src/features/chat/managers/chat-session-list.manager.ts +68 -27
  218. package/src/features/cron/components/__tests__/cron-config.test.tsx +260 -0
  219. package/src/features/cron/components/cron-config.tsx +392 -388
  220. package/src/features/cron/components/cron-job-detail-dialog.tsx +210 -0
  221. package/src/features/cron/components/cron-job-row.tsx +271 -0
  222. package/src/features/cron/components/cron-task-discovery.tsx +157 -0
  223. package/src/features/cron/hooks/use-cron-jobs.ts +3 -1
  224. package/src/features/marketplace/components/__tests__/marketplace-page.test.tsx +3 -2
  225. package/src/features/marketplace/components/marketplace-external-skill-source-action.tsx +8 -6
  226. package/src/features/marketplace/components/mcp/mcp-marketplace-page.tsx +26 -40
  227. package/src/features/pwa/components/pwa-install-entry.tsx +45 -58
  228. package/src/features/remote/components/remote-access-page.tsx +128 -208
  229. package/src/features/settings/components/config/provider-auth-section.tsx +32 -17
  230. package/src/features/settings/components/config/provider-form.tsx +6 -6
  231. package/src/features/settings/components/config/provider-models-section.tsx +52 -46
  232. package/src/features/settings/components/config/secrets-config-form.tsx +78 -84
  233. package/src/features/settings/index.ts +0 -1
  234. package/src/features/settings/pages/__tests__/appearance-settings-page.test.tsx +35 -1
  235. package/src/features/settings/pages/__tests__/model-config-page.test.tsx +18 -1
  236. package/src/features/settings/pages/__tests__/search-config-page.test.tsx +8 -0
  237. package/src/features/settings/pages/__tests__/secrets-config-page.test.tsx +18 -17
  238. package/src/features/settings/pages/appearance-settings-page.tsx +87 -64
  239. package/src/features/settings/pages/model-config-page.tsx +75 -70
  240. package/src/features/settings/pages/providers-config-page.tsx +107 -73
  241. package/src/features/settings/pages/search-config-page.tsx +159 -152
  242. package/src/features/settings/pages/secrets-config-page.tsx +6 -1
  243. package/src/features/system-status/components/config/runtime-agent-list-card.tsx +23 -22
  244. package/src/features/system-status/components/config/runtime-binding-list-card.tsx +23 -22
  245. package/src/features/system-status/components/config/runtime-config-editor.tsx +17 -7
  246. package/src/features/system-status/components/config/runtime-config-overview.tsx +0 -3
  247. package/src/features/system-status/components/config/runtime-entry-list-card.tsx +24 -23
  248. package/src/features/system-status/components/config/runtime-settings-card.tsx +59 -52
  249. package/src/features/system-status/components/desktop-update-config.tsx +117 -112
  250. package/src/features/system-status/components/runtime-control-card.tsx +62 -59
  251. package/src/features/system-status/components/runtime-presence-card.tsx +79 -98
  252. package/src/features/system-status/components/runtime-security-card.tsx +74 -62
  253. package/src/features/system-status/components/security-config.tsx +3 -4
  254. package/src/features/system-status/pages/runtime-config-page.tsx +6 -1
  255. package/src/platforms/desktop/components/desktop-app-shell.tsx +1 -1
  256. package/src/platforms/mobile/components/mobile-settings-shell.tsx +2 -2
  257. package/src/shared/components/__tests__/config-split-page.test.tsx +15 -3
  258. package/src/shared/components/__tests__/settings-page.test.tsx +31 -0
  259. package/src/shared/components/actions/__tests__/navigation-link.test.tsx +47 -0
  260. package/src/shared/components/actions/navigation-link.tsx +116 -0
  261. package/src/shared/components/common/__tests__/brand-header.test.tsx +5 -5
  262. package/src/shared/components/common/__tests__/searchable-model-input.test.tsx +24 -0
  263. package/src/shared/components/common/brand-header.tsx +12 -10
  264. package/src/shared/components/common/searchable-model-input.tsx +9 -10
  265. package/src/shared/components/config-split-page.tsx +70 -65
  266. package/src/shared/components/doc-browser/doc-browser-panel-parts.tsx +6 -7
  267. package/src/shared/components/settings/setting-row.tsx +61 -30
  268. package/src/shared/components/settings/settings-page.tsx +34 -0
  269. package/src/shared/components/ui/actions/__tests__/icon-action-button.test.tsx +23 -2
  270. package/src/shared/components/ui/actions/icon-action-button.tsx +9 -2
  271. package/src/shared/components/ui/overlays/sheet.tsx +142 -0
  272. package/src/shared/lib/api/ncp-session.types.ts +8 -2
  273. package/src/shared/lib/api/types.ts +22 -1
  274. package/src/shared/lib/api/utils/config.utils.ts +2 -1
  275. package/src/shared/lib/api/utils/ncp-session.utils.ts +25 -11
  276. package/src/shared/lib/i18n/locales/en-US/chat.json +32 -1
  277. package/src/shared/lib/i18n/locales/en-US/core.json +3 -3
  278. package/src/shared/lib/i18n/locales/en-US/cron.json +87 -17
  279. package/src/shared/lib/i18n/locales/zh-CN/chat.json +34 -3
  280. package/src/shared/lib/i18n/locales/zh-CN/core.json +6 -6
  281. package/src/shared/lib/i18n/locales/zh-CN/cron.json +80 -10
  282. package/src/shared/lib/transport/__tests__/remote.transport.test.ts +16 -4
  283. package/dist/assets/api-D0U10BcF.js +0 -13
  284. package/dist/assets/app-presenter-provider-Dffdm_Ek.js +0 -106
  285. package/dist/assets/appearance-settings-page-sfusZc2d.js +0 -1
  286. package/dist/assets/architecture-TIHT7OUA-ClwVFq-K.js +0 -1
  287. package/dist/assets/channel-BEHOSFc5.js +0 -1
  288. package/dist/assets/channels-list-page-Dkfe87Zq.js +0 -8
  289. package/dist/assets/chat-page-CeapbQnG.js +0 -1
  290. package/dist/assets/chunk-2Q5K7J3B-DrIhesSX.js +0 -1
  291. package/dist/assets/chunk-VAUOI2AC-Dpcz6BhT.js +0 -1
  292. package/dist/assets/chunk-XXDRQBXY-C2QjjwYX.js +0 -1
  293. package/dist/assets/classDiagram-OUVF2IWQ-DESMaY3_.js +0 -1
  294. package/dist/assets/classDiagram-v2-EOCWNBFH-DwOlByYH.js +0 -1
  295. package/dist/assets/config-split-page-CnQI8cKT.js +0 -1
  296. package/dist/assets/confirm-dialog-RZUWA_j9.js +0 -5
  297. package/dist/assets/cynefin-VYW2F7L2-CzjeeOq9.js +0 -1
  298. package/dist/assets/desktop-update-config-CbSYQMYz.js +0 -1
  299. package/dist/assets/doc-browser-DwXHr-mm.js +0 -1
  300. package/dist/assets/doc-browser-jVTq8iY_2.js +0 -1
  301. package/dist/assets/download-BP7ZxrwI.js +0 -1
  302. package/dist/assets/eventmodeling-45OFAUF4-C38BqG8w.js +0 -1
  303. package/dist/assets/external-link-DvHCEe3h.js +0 -1
  304. package/dist/assets/eye-C--6dxv_.js +0 -1
  305. package/dist/assets/flowDiagram-23GEKE2U-BoKk8RRq.js +0 -1
  306. package/dist/assets/folder-BfeO1zCY.js +0 -1
  307. package/dist/assets/gitGraph-TEB2WS4Q-IsQHHJuS.js +0 -1
  308. package/dist/assets/index-BlKbckX4.css +0 -1
  309. package/dist/assets/index-DoZR11Nh.js +0 -2
  310. package/dist/assets/info-DKCQHKI2-ofy2qlHI.js +0 -1
  311. package/dist/assets/loader-circle-BWdgwVnn.js +0 -1
  312. package/dist/assets/mcp-marketplace-page-XnRWiC8c.js +0 -1
  313. package/dist/assets/model-config-page-CXnqSbSI.js +0 -1
  314. package/dist/assets/packet-7NZHBO7P-Rfopfa1o.js +0 -1
  315. package/dist/assets/pie-RZYD4A2V-CJR1xKEv.js +0 -1
  316. package/dist/assets/plus-BeApS21t.js +0 -1
  317. package/dist/assets/provider-scoped-model-input-ll_Btc9h.js +0 -1
  318. package/dist/assets/providers-config-page-Bcuhqkdl.js +0 -1
  319. package/dist/assets/radar-I7S5WNFK-CDg8Z-1r.js +0 -1
  320. package/dist/assets/railroad-3IZDKUUU-BHnNmjxO.js +0 -1
  321. package/dist/assets/railroad-abnf-AHOZXSZD-DGtNcusW.js +0 -1
  322. package/dist/assets/railroad-ebnf-EBAXGLYW-BRFtsQmm.js +0 -1
  323. package/dist/assets/railroad-peg-LSFZ7HO6-Bj5IypCA.js +0 -1
  324. package/dist/assets/remote-CqKpuOP9.js +0 -1
  325. package/dist/assets/rotate-cw-DHXoiqFB.js +0 -1
  326. package/dist/assets/runtime-config-page-D-Aqxizs.js +0 -1
  327. package/dist/assets/search-config-page-XTKf5rc0.js +0 -1
  328. package/dist/assets/secrets-config-page-Cx-qptcj.js +0 -3
  329. package/dist/assets/security-config-DWGFM2u9.js +0 -1
  330. package/dist/assets/setting-row-DFvioO2a.js +0 -1
  331. package/dist/assets/settings-2-RMWqf-ey.js +0 -1
  332. package/dist/assets/skeleton-DQ5WhuDG.js +0 -1
  333. package/dist/assets/stateDiagram-v2-6OUMAXLB-CfMU38yc.js +0 -1
  334. package/dist/assets/swimlanesDiagram-G3AALYLV-D9Zb9X57.js +0 -8
  335. package/dist/assets/treeView-QDETBFTQ-Uy7M0WvH.js +0 -1
  336. package/dist/assets/treemap-6X3UGDF4-BJfGV7KX.js +0 -1
  337. package/dist/assets/use-confirm-dialog-CTNA1PC0.js +0 -1
  338. package/dist/assets/wardley-OPB4EBWU-q263iF3K.js +0 -1
  339. package/dist/assets/x-qvh05iyu.js +0 -1
  340. package/src/features/chat/components/conversation/chat-conversation-skeleton.tsx +0 -86
  341. package/src/features/settings/pages/__tests__/language-settings-page.test.tsx +0 -36
  342. package/src/features/settings/pages/language-settings-page.tsx +0 -56
  343. package/src/shared/components/actions/action-link.tsx +0 -49
  344. /package/dist/assets/{array-BqYdCIE6.js → array-CAkJT3no.js} +0 -0
  345. /package/dist/assets/{chunk-KEIR6QF5-A3jEqwzn.js → chunk-KEIR6QF5-BgdDoWEH.js} +0 -0
  346. /package/dist/assets/{chunk-Y2CYZVJY-BGrd8tLy.js → chunk-Y2CYZVJY-pTllD0P3.js} +0 -0
  347. /package/dist/assets/{config-hints-LxG80Uj5.js → config-hints-S3g-UVZb.js} +0 -0
  348. /package/dist/assets/{cytoscape.esm-CRW8q92n.js → cytoscape.esm-DmURt-5G.js} +0 -0
  349. /package/dist/assets/{defaultLocale-Cdv9x6q7.js → defaultLocale-BKy_KP3O.js} +0 -0
  350. /package/dist/assets/{dist-dw-gAfM_.js → dist-IgNNB7K9.js} +0 -0
  351. /package/dist/assets/{graphlib-BcNFQr5y.js → graphlib-xw_TBHyH.js} +0 -0
  352. /package/dist/assets/{host-capabilities-CFbAfrrA.js → host-capabilities-B-TtABsP.js} +0 -0
  353. /package/dist/assets/{init-D8TjIqfm.js → init-C9xlLIAu.js} +0 -0
  354. /package/dist/assets/{jszip.min-CP5uPNQa.js → jszip.min-Das2IAnb.js} +0 -0
  355. /package/dist/assets/{katex-wEmrEFTg.js → katex-DSy1eD2d.js} +0 -0
  356. /package/dist/assets/{middleware-CAXxl-af.js → middleware-Df-a4x-q.js} +0 -0
  357. /package/dist/assets/{path-DzBnMI1r.js → path-DB0USYHi.js} +0 -0
  358. /package/dist/assets/{provider-models-BoUhFo9R.js → provider-models-CXUgDxGR.js} +0 -0
  359. /package/dist/assets/{rough.esm-wJ1d3nQl.js → rough.esm-B1B52ZQS.js} +0 -0
  360. /package/dist/assets/{xlsx-DUqJhg5u.js → xlsx-DfjC_iml.js} +0 -0
@@ -0,0 +1,47 @@
1
+ import { fireEvent, render, screen } from "@testing-library/react";
2
+ import { beforeEach, describe, expect, it, vi } from "vitest";
3
+ import { NavigationLink } from "@/shared/components/actions/navigation-link";
4
+
5
+ const mocks = vi.hoisted(() => ({
6
+ openExternalUrl: vi.fn(),
7
+ }));
8
+
9
+ vi.mock("@/shared/lib/host-capabilities", () => ({
10
+ hostCapabilityManager: {
11
+ openExternalUrl: mocks.openExternalUrl,
12
+ },
13
+ }));
14
+
15
+ describe("NavigationLink", () => {
16
+ beforeEach(() => {
17
+ mocks.openExternalUrl.mockReset();
18
+ });
19
+
20
+ it("exposes external destinations as links and delegates normal clicks to the host", () => {
21
+ render(
22
+ <NavigationLink href="https://docs.nextclaw.io/" external>
23
+ View docs
24
+ </NavigationLink>,
25
+ );
26
+
27
+ const link = screen.getByRole("link", { name: "View docs" });
28
+ expect(link.getAttribute("href")).toBe("https://docs.nextclaw.io/");
29
+ expect(link.getAttribute("target")).toBe("_blank");
30
+ expect(link.getAttribute("rel")).toContain("noopener");
31
+ expect(screen.queryByRole("button", { name: "View docs" })).toBeNull();
32
+
33
+ fireEvent.click(link);
34
+
35
+ expect(mocks.openExternalUrl).toHaveBeenCalledWith(
36
+ "https://docs.nextclaw.io/",
37
+ );
38
+ });
39
+
40
+ it("keeps same-page links on the browser navigation path", () => {
41
+ render(<NavigationLink href="/settings">Settings</NavigationLink>);
42
+
43
+ const link = screen.getByRole("link", { name: "Settings" });
44
+ expect(link.getAttribute("target")).toBeNull();
45
+ expect(mocks.openExternalUrl).not.toHaveBeenCalled();
46
+ });
47
+ });
@@ -0,0 +1,116 @@
1
+ import type {
2
+ ComponentPropsWithoutRef,
3
+ MouseEvent,
4
+ ReactNode,
5
+ } from "react";
6
+ import { ExternalLink as ExternalLinkIcon, type LucideIcon } from "lucide-react";
7
+ import { hostCapabilityManager } from "@/shared/lib/host-capabilities";
8
+ import { cn } from "@/shared/lib/utils";
9
+
10
+ type NavigationLinkProps = Omit<
11
+ ComponentPropsWithoutRef<"a">,
12
+ "children" | "href"
13
+ > & {
14
+ children: ReactNode;
15
+ external?: boolean;
16
+ href: string;
17
+ icon?: LucideIcon | null;
18
+ iconPosition?: "leading" | "trailing";
19
+ size?: "xs" | "sm";
20
+ };
21
+
22
+ const LINK_SIZE_CLASS = {
23
+ xs: "gap-1.5 text-xs",
24
+ sm: "gap-1.5 text-sm",
25
+ } as const;
26
+
27
+ const LINK_ICON_SIZE_CLASS = {
28
+ xs: "h-3.5 w-3.5",
29
+ sm: "h-4 w-4",
30
+ } as const;
31
+
32
+ function shouldUseHostNavigation(event: MouseEvent<HTMLAnchorElement>): boolean {
33
+ return (
34
+ event.button === 0 &&
35
+ !event.altKey &&
36
+ !event.ctrlKey &&
37
+ !event.metaKey &&
38
+ !event.shiftKey
39
+ );
40
+ }
41
+
42
+ function resolveExternalRel(rel?: string): string {
43
+ return Array.from(
44
+ new Set(`${rel ?? ""} noopener noreferrer`.trim().split(/\s+/)),
45
+ ).join(" ");
46
+ }
47
+
48
+ function resolveExternalHttpUrl(href: string): string | null {
49
+ try {
50
+ const url = new URL(href, window.location.href);
51
+ return url.protocol === "http:" || url.protocol === "https:"
52
+ ? url.toString()
53
+ : null;
54
+ } catch {
55
+ return null;
56
+ }
57
+ }
58
+
59
+ /**
60
+ * Shared navigation primitive for visible URL destinations.
61
+ * External links retain native link semantics while delegating ordinary clicks
62
+ * to the desktop host when one is available.
63
+ */
64
+ export function NavigationLink({
65
+ children,
66
+ className,
67
+ external = false,
68
+ href,
69
+ icon,
70
+ iconPosition = "leading",
71
+ onClick,
72
+ rel,
73
+ size = "sm",
74
+ target,
75
+ ...props
76
+ }: NavigationLinkProps) {
77
+ const Icon = icon === undefined && external ? ExternalLinkIcon : icon;
78
+ const linkIcon = Icon ? (
79
+ <Icon
80
+ aria-hidden="true"
81
+ className={cn("shrink-0", LINK_ICON_SIZE_CLASS[size])}
82
+ />
83
+ ) : null;
84
+
85
+ return (
86
+ <a
87
+ {...props}
88
+ href={href}
89
+ target={external ? target ?? "_blank" : target}
90
+ rel={external ? resolveExternalRel(rel) : rel}
91
+ className={cn(
92
+ "inline-flex items-center rounded-sm font-medium text-primary underline-offset-4 transition-colors hover:text-primary-hover hover:underline focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/25",
93
+ LINK_SIZE_CLASS[size],
94
+ className,
95
+ )}
96
+ onClick={(event) => {
97
+ onClick?.(event);
98
+ const externalHttpUrl = external
99
+ ? resolveExternalHttpUrl(href)
100
+ : null;
101
+ if (
102
+ externalHttpUrl &&
103
+ !event.defaultPrevented &&
104
+ shouldUseHostNavigation(event)
105
+ ) {
106
+ event.preventDefault();
107
+ void hostCapabilityManager.openExternalUrl(externalHttpUrl);
108
+ }
109
+ }}
110
+ >
111
+ {iconPosition === "leading" ? linkIcon : null}
112
+ {children}
113
+ {iconPosition === "trailing" ? linkIcon : null}
114
+ </a>
115
+ );
116
+ }
@@ -128,7 +128,7 @@ describe('BrandHeader', () => {
128
128
  const downloadProgress = screen.getByText('下载 50%');
129
129
  expect(downloadProgress).toBeTruthy();
130
130
  expect(screen.queryByRole('button', { name: '更新' })).toBeNull();
131
- const progressReleaseNotes = screen.getByRole('button', { name: '查看 v0.18.12 更新说明' });
131
+ const progressReleaseNotes = screen.getByRole('link', { name: '查看 v0.18.12 更新说明' });
132
132
  expect(progressReleaseNotes.parentElement?.className).toContain('invisible');
133
133
  expect(progressReleaseNotes.parentElement?.className).toContain('group-hover/update-release-notes:visible');
134
134
 
@@ -147,7 +147,7 @@ describe('BrandHeader', () => {
147
147
 
148
148
  renderBrandHeader({ productVersion: '0.23.0' });
149
149
 
150
- const version = await screen.findByRole('button', {
150
+ const version = await screen.findByRole('link', {
151
151
  name: '当前版本 v0.23.0,查看 v0.23.0 更新说明'
152
152
  });
153
153
 
@@ -194,14 +194,14 @@ describe('BrandHeader', () => {
194
194
 
195
195
  renderBrandHeader();
196
196
 
197
- const currentVersion = await screen.findByRole('button', {
197
+ const currentVersion = await screen.findByRole('link', {
198
198
  name: '当前版本 v0.18.11,查看 v0.18.11 更新说明'
199
199
  });
200
200
  await user.click(currentVersion);
201
201
  expect(mocks.openExternalUrl).toHaveBeenCalledWith('https://docs.nextclaw.io/zh/notes/2026-07-14-nextclaw-v0-18-11');
202
202
 
203
203
  const downloadUpdate = screen.getByRole('button', { name: '下载' });
204
- const updateReleaseNotes = screen.getByRole('button', { name: '查看 v0.18.12 更新说明' });
204
+ const updateReleaseNotes = screen.getByRole('link', { name: '查看 v0.18.12 更新说明' });
205
205
  expect(updateReleaseNotes.parentElement?.className).toContain('group-hover/update-release-notes:visible');
206
206
 
207
207
  await user.hover(downloadUpdate);
@@ -241,7 +241,7 @@ describe('BrandHeader', () => {
241
241
  renderBrandHeader();
242
242
 
243
243
  const applyUpdate = screen.getByRole('button', { name: '更新' });
244
- const updateReleaseNotes = screen.getByRole('button', { name: '查看 v0.18.12 更新说明' });
244
+ const updateReleaseNotes = screen.getByRole('link', { name: '查看 v0.18.12 更新说明' });
245
245
  expect(updateReleaseNotes.parentElement?.className).toContain('group-focus-within/update-release-notes:visible');
246
246
 
247
247
  fireEvent.focus(applyUpdate);
@@ -1,9 +1,33 @@
1
1
  import { fireEvent, render, screen } from '@testing-library/react';
2
+ import userEvent from '@testing-library/user-event';
2
3
  import { describe, expect, it, vi } from 'vitest';
3
4
  import { SearchableModelInput } from '@/shared/components/common/searchable-model-input';
4
5
  import { createPopoverAvailableHeightLimit } from '@/shared/components/ui/popover';
5
6
 
6
7
  describe('SearchableModelInput', () => {
8
+ it('keeps the option panel open when the focused input is clicked again', async () => {
9
+ const user = userEvent.setup();
10
+
11
+ render(
12
+ <SearchableModelInput
13
+ value="model-1"
14
+ onChange={vi.fn()}
15
+ options={['model-1', 'model-2']}
16
+ placeholder="Model"
17
+ emptyText="No models"
18
+ createText="Use {value}"
19
+ toggleLabel="Toggle models"
20
+ />
21
+ );
22
+
23
+ const input = screen.getByPlaceholderText('Model');
24
+ await user.click(input);
25
+ expect(await screen.findByRole('button', { name: 'model-1' })).toBeTruthy();
26
+
27
+ await user.click(input);
28
+ expect(screen.getByRole('button', { name: 'model-1' })).toBeTruthy();
29
+ });
30
+
7
31
  it('keeps the model option panel bounded with an internal scroll region', async () => {
8
32
  render(
9
33
  <SearchableModelInput
@@ -10,8 +10,8 @@ import {
10
10
  import { useAppMeta } from '@/shared/hooks/use-app-meta';
11
11
  import type { ReactNode } from 'react';
12
12
  import { RuntimeStatusEntry } from '@/app/components/layout/runtime-status-entry';
13
+ import { NavigationLink } from '@/shared/components/actions/navigation-link';
13
14
  import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/shared/components/ui/tooltip';
14
- import { hostCapabilityManager } from '@/shared/lib/host-capabilities';
15
15
  import { t } from '@/shared/lib/i18n';
16
16
  import { cn } from '@/shared/lib/utils';
17
17
 
@@ -83,14 +83,15 @@ function BrandVersionLabel({
83
83
  ].join('\n')
84
84
  : versionLabel;
85
85
  const trigger = releaseNotesLink ? (
86
- <button
87
- type="button"
86
+ <NavigationLink
87
+ href={releaseNotesLink.url}
88
+ external
89
+ icon={null}
88
90
  aria-label={t('desktopUpdatesVersionReleaseNotesLabel').replace('{currentVersion}', versionLabel).replace('{releaseVersion}', releaseNotesLink.versionLabel)}
89
- className={cn(triggerClassName, 'border-0 bg-transparent p-0 text-left')}
90
- onClick={() => void hostCapabilityManager.openExternalUrl(releaseNotesLink.url)}
91
+ className={cn(triggerClassName, 'p-0 text-left')}
91
92
  >
92
93
  {versionLabel}
93
- </button>
94
+ </NavigationLink>
94
95
  ) : (
95
96
  <span tabIndex={0} aria-label={versionLabel} className={triggerClassName}>
96
97
  {versionLabel}
@@ -175,14 +176,15 @@ function RuntimeUpdateReleaseNotesHover({
175
176
  data-update-release-notes-hover="true"
176
177
  className="invisible pointer-events-none absolute left-1/2 top-full z-[var(--z-tooltip,10150)] -translate-x-1/2 pt-1 opacity-0 transition-opacity group-hover/update-release-notes:visible group-hover/update-release-notes:pointer-events-auto group-hover/update-release-notes:opacity-100 group-focus-within/update-release-notes:visible group-focus-within/update-release-notes:pointer-events-auto group-focus-within/update-release-notes:opacity-100"
177
178
  >
178
- <button
179
- type="button"
179
+ <NavigationLink
180
+ href={link.url}
181
+ external
182
+ icon={null}
180
183
  aria-label={label}
181
184
  className="whitespace-nowrap rounded-md border bg-popover px-3 py-1.5 text-left text-xs font-medium text-popover-foreground shadow-md outline-none transition-colors hover:bg-gray-100 focus-visible:ring-2 focus-visible:ring-gray-300"
182
- onClick={() => void hostCapabilityManager.openExternalUrl(link.url)}
183
185
  >
184
186
  {label}
185
- </button>
187
+ </NavigationLink>
186
188
  </span>
187
189
  </span>
188
190
  );
@@ -1,4 +1,4 @@
1
- import { useEffect, useMemo, useState } from 'react';
1
+ import { useEffect, useMemo, useRef, useState } from 'react';
2
2
  import { Check, ChevronsUpDown } from 'lucide-react';
3
3
  import { Input } from '@/shared/components/ui/input';
4
4
  import {
@@ -28,14 +28,7 @@ type SearchableModelInputProps = {
28
28
  const SEARCHABLE_MODEL_MENU_MAX_HEIGHT = createPopoverAvailableHeightLimit('15rem');
29
29
 
30
30
  function normalizeOptions(options: string[]): string[] {
31
- const deduped = new Set<string>();
32
- for (const option of options) {
33
- const trimmed = option.trim();
34
- if (trimmed.length > 0) {
35
- deduped.add(trimmed);
36
- }
37
- }
38
- return [...deduped];
31
+ return [...new Set(options.map((option) => option.trim()).filter(Boolean))];
39
32
  }
40
33
 
41
34
  export function SearchableModelInput({
@@ -54,6 +47,7 @@ export function SearchableModelInput({
54
47
  onEnter
55
48
  }: SearchableModelInputProps) {
56
49
  const [open, setOpen] = useState(false);
50
+ const anchorRef = useRef<HTMLDivElement>(null);
57
51
 
58
52
  useEffect(() => {
59
53
  if (disabled && open) {
@@ -95,7 +89,7 @@ export function SearchableModelInput({
95
89
  onOpenChange={(nextOpen) => setOpen(!disabled && nextOpen)}
96
90
  >
97
91
  <PopoverAnchor asChild>
98
- <div className={cn('relative', className)}>
92
+ <div ref={anchorRef} className={cn('relative', className)}>
99
93
  <Input
100
94
  id={id}
101
95
  value={value}
@@ -141,6 +135,11 @@ export function SearchableModelInput({
141
135
  <PopoverContent
142
136
  align="start"
143
137
  className="flex w-[var(--radix-popover-trigger-width)] min-w-[12rem] flex-col overflow-hidden rounded-xl border border-gray-200 p-0 shadow-lg"
138
+ onInteractOutside={(event) => {
139
+ if (event.target instanceof Node && anchorRef.current?.contains(event.target)) {
140
+ event.preventDefault();
141
+ }
142
+ }}
144
143
  onOpenAutoFocus={(event) => event.preventDefault()}
145
144
  style={{ maxHeight: SEARCHABLE_MODEL_MENU_MAX_HEIGHT }}
146
145
  >
@@ -1,87 +1,92 @@
1
- import { Children, type ButtonHTMLAttributes, type HTMLAttributes } from "react";
2
- import { ArrowLeft, type LucideIcon } from "lucide-react";
3
- import { t } from "@/shared/lib/i18n";
4
- import { cn } from "@/shared/lib/utils";
1
+ import { Children, type ButtonHTMLAttributes, type HTMLAttributes, useEffect, useRef, useState } from 'react';
2
+ import { ArrowLeft, type LucideIcon } from 'lucide-react';
3
+ import { t } from '@/shared/lib/i18n';
4
+ import { cn } from '@/shared/lib/utils';
5
5
 
6
- const CARD_CLASS = "min-w-0 overflow-hidden rounded-2xl border border-border/75 bg-card text-card-foreground xl:h-[calc(100vh-180px)] xl:max-h-[860px]";
7
- type DivProps = HTMLAttributes<HTMLDivElement>; type SectionProps = HTMLAttributes<HTMLElement>;
8
-
9
- function ConfigSplitPane({ className, ...props }: SectionProps) {
10
- return <section className={cn(CARD_CLASS, "flex flex-col", className)} {...props} />;
11
- }
6
+ const PANE_CLASS = 'min-w-0 overflow-hidden bg-card/70 text-card-foreground md:h-[calc(100vh-180px)] md:max-h-[860px]';
7
+ const SHELL_CLASS = 'min-h-0 overflow-hidden rounded-2xl bg-muted/30 ring-1 ring-border/45';
8
+ const COMPACT_WIDTH = 640;
9
+ type DivProps = HTMLAttributes<HTMLDivElement>;
10
+ type SectionProps = HTMLAttributes<HTMLElement>;
12
11
 
13
12
  export function ConfigSplitPage({
14
13
  className,
15
14
  children,
16
- mobileView,
15
+ compactView,
17
16
  onMobileBack,
18
17
  mobileListLabel,
19
18
  ...props
20
19
  }: DivProps & {
21
- mobileView?: "list" | "detail";
20
+ compactView?: 'list' | 'detail';
22
21
  onMobileBack?: () => void;
23
22
  mobileListLabel?: string;
24
23
  }) {
24
+ const rootRef = useRef<HTMLDivElement>(null);
25
+ const [isCompact, setIsCompact] = useState(false);
25
26
  const childArray = Children.toArray(children);
26
27
  const [sidebarChild, detailChild, ...remainingChildren] = childArray;
27
28
 
28
- if (mobileView && sidebarChild && detailChild) {
29
- return (
30
- <div
31
- className={cn("grid min-h-0 grid-cols-1 gap-4", className)}
32
- {...props}
33
- >
34
- {mobileView === "detail" ? (
35
- <>
36
- <div className="shrink-0">
29
+ useEffect(() => {
30
+ const root = rootRef.current;
31
+ if (!root || !compactView || typeof ResizeObserver === 'undefined') return;
32
+ const update = () => setIsCompact(root.clientWidth > 0 && root.clientWidth < COMPACT_WIDTH);
33
+ const observer = new ResizeObserver(update);
34
+ observer.observe(root);
35
+ update();
36
+ return () => observer.disconnect();
37
+ }, [compactView]);
38
+
39
+ const shouldUseCompactView = Boolean(isCompact && compactView && sidebarChild && detailChild);
40
+
41
+ return (
42
+ <div ref={rootRef} className={cn('min-h-0 md:flex-1', className)} {...props}>
43
+ {shouldUseCompactView ? (
44
+ <div className='space-y-3'>
45
+ {compactView === 'detail' ? (
46
+ <>
37
47
  <button
38
- type="button"
48
+ type='button'
39
49
  onClick={onMobileBack}
40
- className="inline-flex items-center gap-2 rounded-full border border-border bg-card px-3 py-2 text-sm font-medium text-muted-foreground transition-colors hover:bg-accent hover:text-accent-foreground"
50
+ className='inline-flex items-center gap-2 rounded-lg px-2 py-1.5 text-sm font-medium text-muted-foreground transition-colors hover:bg-muted hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/40'
41
51
  >
42
- <ArrowLeft className="h-4 w-4" />
43
- <span>{mobileListLabel ?? t("backToMain")}</span>
52
+ <ArrowLeft className='h-4 w-4' />
53
+ <span>{mobileListLabel ?? t('backToMain')}</span>
44
54
  </button>
45
- </div>
46
- {detailChild}
47
- {remainingChildren}
48
- </>
49
- ) : (
50
- <>
51
- {sidebarChild}
52
- {remainingChildren}
53
- </>
54
- )}
55
- </div>
56
- );
57
- }
58
-
59
- return (
60
- <div
61
- className={cn(
62
- "grid min-h-0 grid-cols-1 gap-5 xl:flex-1 xl:grid-cols-[340px_minmax(0,1fr)]",
63
- className,
55
+ <div className={SHELL_CLASS}>{detailChild}</div>
56
+ </>
57
+ ) : (
58
+ <div className={SHELL_CLASS}>{sidebarChild}</div>
59
+ )}
60
+ {remainingChildren}
61
+ </div>
62
+ ) : (
63
+ <div className={cn(SHELL_CLASS, 'grid grid-cols-1 md:grid-cols-[minmax(240px,30%)_minmax(0,1fr)]')}>
64
+ {children}
65
+ </div>
64
66
  )}
65
- {...props}
66
- >
67
- {children}
68
67
  </div>
69
68
  );
70
69
  }
71
70
 
72
- export { ConfigSplitPane as ConfigSplitSidebar, ConfigSplitPane as ConfigSplitDetailPane };
71
+ export function ConfigSplitSidebar({ className, ...props }: SectionProps) {
72
+ return <section className={cn(PANE_CLASS, 'flex flex-col md:border-r md:border-border/55', className)} {...props} />;
73
+ }
74
+
75
+ export function ConfigSplitDetailPane({ className, ...props }: SectionProps) {
76
+ return <section className={cn(PANE_CLASS, 'flex flex-col', className)} {...props} />;
77
+ }
73
78
 
74
79
  export function ConfigSplitEmptyPane({ className, ...props }: SectionProps) {
75
80
  return (
76
81
  <section
77
- className={cn(CARD_CLASS, "flex items-center justify-center px-6 py-12 text-center", className)}
82
+ className={cn(PANE_CLASS, 'flex items-center justify-center px-6 py-12 text-center', className)}
78
83
  {...props}
79
84
  />
80
85
  );
81
86
  }
82
87
 
83
88
  export function ConfigSplitPaneHeader({ className, ...props }: DivProps) {
84
- return <div className={cn("shrink-0 border-b border-border/70", className)} {...props} />;
89
+ return <div className={cn('shrink-0 border-b border-border/70', className)} {...props} />;
85
90
  }
86
91
 
87
92
  export function ConfigSplitPaneBody({
@@ -92,9 +97,9 @@ export function ConfigSplitPaneBody({
92
97
  return (
93
98
  <div
94
99
  className={cn(
95
- "min-h-0 flex-1",
96
- scrollOnDesktop && "overflow-visible xl:overflow-y-auto xl:overscroll-contain",
97
- className,
100
+ 'min-h-0 flex-1',
101
+ scrollOnDesktop && 'overflow-visible md:overflow-y-auto md:overscroll-contain',
102
+ className
98
103
  )}
99
104
  {...props}
100
105
  />
@@ -102,24 +107,24 @@ export function ConfigSplitPaneBody({
102
107
  }
103
108
 
104
109
  export function ConfigSplitPaneFooter({ className, ...props }: DivProps) {
105
- return <div className={cn("shrink-0 border-t border-border/70 px-4 py-2.5", className)} {...props} />;
110
+ return <div className={cn('shrink-0 border-t border-border/70 px-4 py-2.5', className)} {...props} />;
106
111
  }
107
112
 
108
113
  export function ConfigSelectionCard({
109
114
  active = false,
110
115
  className,
111
- type = "button",
116
+ type = 'button',
112
117
  ...props
113
118
  }: ButtonHTMLAttributes<HTMLButtonElement> & { active?: boolean }) {
114
119
  return (
115
120
  <button
116
121
  type={type}
117
122
  className={cn(
118
- "w-full rounded-xl border p-2.5 text-left transition-colors",
123
+ 'w-full rounded-xl p-2.5 text-left transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/40',
119
124
  active
120
- ? "border-border/70 bg-muted/65 text-foreground"
121
- : "border-transparent bg-transparent text-muted-foreground hover:border-border/50 hover:bg-muted/40 hover:text-foreground",
122
- className,
125
+ ? 'bg-background/95 text-foreground shadow-sm'
126
+ : 'bg-transparent text-muted-foreground hover:bg-background/65 hover:text-foreground',
127
+ className
123
128
  )}
124
129
  {...props}
125
130
  />
@@ -140,16 +145,16 @@ export function ConfigSplitEmptyState({
140
145
  return (
141
146
  <div
142
147
  className={cn(
143
- "flex min-h-[220px] flex-col items-center justify-center rounded-xl border border-dashed border-border/70 bg-muted/35 px-4 py-10 text-center",
144
- className,
148
+ 'flex min-h-[220px] flex-col items-center justify-center rounded-xl bg-background/55 px-4 py-10 text-center',
149
+ className
145
150
  )}
146
151
  {...props}
147
152
  >
148
- <div className="mb-3 flex h-10 w-10 items-center justify-center rounded-lg bg-card">
149
- <Icon className="h-5 w-5 text-muted-foreground/45" />
153
+ <div className='mb-3 flex h-10 w-10 items-center justify-center rounded-lg bg-card'>
154
+ <Icon className='h-5 w-5 text-muted-foreground/45' />
150
155
  </div>
151
- <p className="text-sm font-medium text-foreground">{title}</p>
152
- {description ? <p className="mt-2 text-xs text-muted-foreground">{description}</p> : null}
156
+ <p className='text-sm font-medium text-foreground'>{title}</p>
157
+ {description ? <p className='mt-2 text-xs text-muted-foreground'>{description}</p> : null}
153
158
  </div>
154
159
  );
155
160
  }
@@ -1,6 +1,5 @@
1
1
  import type { PointerEventHandler, ReactNode, Ref } from 'react';
2
2
  import {
3
- ExternalLink,
4
3
  RefreshCw,
5
4
  Search,
6
5
  } from 'lucide-react';
@@ -9,6 +8,7 @@ import {
9
8
  type DocBrowserTab,
10
9
  } from './doc-browser-context';
11
10
  import { IconActionButton } from '@/shared/components/ui/actions/icon-action-button';
11
+ import { NavigationLink } from '@/shared/components/actions/navigation-link';
12
12
  import { t } from '@/shared/lib/i18n';
13
13
 
14
14
  type DocBrowserAddressToolbarProps = {
@@ -119,16 +119,15 @@ export function DocBrowserExternalLink({ currentUrl, isVisible }: { currentUrl:
119
119
 
120
120
  return (
121
121
  <div className="flex items-center justify-between border-t border-border/70 bg-muted/55 px-4 py-2 shrink-0">
122
- <a
122
+ <NavigationLink
123
123
  href={currentUrl}
124
- target="_blank"
125
- rel="noopener noreferrer"
124
+ external
125
+ iconPosition="trailing"
126
+ size="xs"
126
127
  data-doc-external
127
- className="flex items-center gap-1.5 text-xs text-primary hover:text-primary-hover font-medium transition-colors"
128
128
  >
129
129
  {label}
130
- <ExternalLink className="w-3 h-3" />
131
- </a>
130
+ </NavigationLink>
132
131
  </div>
133
132
  );
134
133
  }