@nextclaw/ui 0.15.9 → 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 +100 -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
@@ -4,6 +4,7 @@ import { eventKeys } from '@nextclaw/shared';
4
4
  import { BookOpen, ChevronDown } from 'lucide-react';
5
5
  import { toast } from 'sonner';
6
6
  import { LogoBadge } from '@/shared/components/common/logo-badge';
7
+ import { NavigationLink } from '@/shared/components/actions/navigation-link';
7
8
  import { Button } from '@/shared/components/ui/button';
8
9
  import { FormActions } from '@/shared/components/ui/actions/form-actions';
9
10
  import { StatusDot } from '@/shared/components/status/status-dot';
@@ -136,26 +137,25 @@ function ChannelFormHeader({
136
137
  tutorialUrl?: string;
137
138
  }) {
138
139
  return (
139
- <ConfigSplitPaneHeader className="px-6 py-5">
140
- <div className="flex flex-wrap items-center justify-between gap-3">
141
- <div className="min-w-0">
142
- <div className="flex items-center gap-3">
140
+ <ConfigSplitPaneHeader className='px-5 py-4'>
141
+ <div className='flex flex-wrap items-center justify-between gap-3'>
142
+ <div className='min-w-0'>
143
+ <div className='flex items-center gap-3'>
143
144
  <LogoBadge
144
145
  name={channelName}
145
146
  src={getChannelLogo(channelName)}
146
- className={cn('h-9 w-9 rounded-lg border', enabled ? 'border-primary/30 bg-white' : 'border-gray-200/70 bg-white')}
147
- imgClassName="h-5 w-5 object-contain"
148
- fallback={<span className="text-sm font-semibold uppercase text-gray-500">{channelName[0]}</span>}
147
+ className={cn('h-9 w-9 rounded-lg bg-background/80', enabled && 'ring-1 ring-primary/20')}
148
+ imgClassName='h-5 w-5 object-contain'
149
+ fallback={<span className='text-sm font-semibold uppercase text-muted-foreground'>{channelName[0]}</span>}
149
150
  />
150
- <h3 className="truncate text-lg font-semibold text-gray-900 capitalize">{channelLabel}</h3>
151
+ <h3 className='truncate text-lg font-semibold text-foreground capitalize'>{channelLabel}</h3>
151
152
  </div>
152
- <p className="mt-2 text-sm text-gray-500">{t('channelsFormDescription')}</p>
153
+ <p className='mt-2 text-sm text-muted-foreground'>{t('channelsFormDescription')}</p>
153
154
  {channelApplyStatus ? <p className={cn('mt-2 text-xs font-medium', channelApplyStatus.className)}>{channelApplyStatus.label}</p> : null}
154
155
  {tutorialUrl ? (
155
- <a href={tutorialUrl} className="mt-2 inline-flex items-center gap-1.5 text-xs text-primary transition-colors hover:text-primary-hover">
156
- <BookOpen className="h-3.5 w-3.5" />
156
+ <NavigationLink href={tutorialUrl} external icon={BookOpen} size='xs' className='mt-2'>
157
157
  {t('channelsGuideTitle')}
158
- </a>
158
+ </NavigationLink>
159
159
  ) : null}
160
160
  </div>
161
161
  <StatusDot status={enabled ? 'active' : 'inactive'} label={enabled ? t('statusActive') : t('statusInactive')} />
@@ -212,15 +212,15 @@ function ChannelFormBlocks({
212
212
  return <div key={`${block.type}-${block.section}-${index}`}>{content}</div>;
213
213
  }
214
214
  return (
215
- <details key={`${block.type}-${block.section}-${index}`} className="group rounded-2xl border border-gray-200/80 bg-white">
216
- <summary className="flex cursor-pointer list-none items-center justify-between gap-3 px-5 py-4 text-sm font-medium text-gray-900">
215
+ <details key={`${block.type}-${block.section}-${index}`} className='group rounded-xl bg-muted/35'>
216
+ <summary className='flex cursor-pointer list-none items-center justify-between gap-3 px-4 py-3 text-sm font-medium text-foreground'>
217
217
  <div>
218
218
  <p>{block.collapsible.title}</p>
219
- {block.collapsible.description ? <p className="mt-1 text-xs font-normal text-gray-500">{block.collapsible.description}</p> : null}
219
+ {block.collapsible.description ? <p className='mt-1 text-xs font-normal text-muted-foreground'>{block.collapsible.description}</p> : null}
220
220
  </div>
221
- <ChevronDown className="h-4 w-4 text-gray-400 transition-transform group-open:rotate-180" />
221
+ <ChevronDown className='h-4 w-4 text-muted-foreground transition-transform group-open:rotate-180' />
222
222
  </summary>
223
- <div className="space-y-6 border-t border-gray-100 px-5 py-5">{content}</div>
223
+ <div className='space-y-5 border-t border-border/45 px-4 py-4'>{content}</div>
224
224
  </details>
225
225
  );
226
226
  }
@@ -240,7 +240,7 @@ function ChannelFormBlocks({
240
240
  key={`${block.type}-${block.sectionId}-${index}`}
241
241
  channelConfig={channelConfig}
242
242
  formData={formData}
243
- channelName="weixin"
243
+ channelName='weixin'
244
244
  channelEnabled={enabled}
245
245
  disabled={disabled}
246
246
  />
@@ -347,8 +347,8 @@ function ChannelFormEditor({
347
347
  channelApplyStatus={channelApplyStatus}
348
348
  />
349
349
 
350
- <form onSubmit={handleSubmit} className="flex min-h-0 flex-1 flex-col">
351
- <ConfigSplitPaneBody className="space-y-6 px-6 py-5">
350
+ <form onSubmit={handleSubmit} className='flex min-h-0 flex-1 flex-col'>
351
+ <ConfigSplitPaneBody className='space-y-5 px-5 py-4'>
352
352
  <ChannelFormBlocks
353
353
  channelConfig={channelConfig}
354
354
  channelName={channelName}
@@ -365,15 +365,15 @@ function ChannelFormEditor({
365
365
  </ConfigSplitPaneBody>
366
366
 
367
367
  <ConfigSplitPaneFooter>
368
- <FormActions align="between">
369
- <div className="flex flex-wrap items-center gap-2">
368
+ <FormActions align='between'>
369
+ <div className='flex flex-wrap items-center gap-2'>
370
370
  {actions.filter((action) => action.trigger === 'manual').map((action) => (
371
- <Button key={action.id} type="button" size="sm" onClick={() => void handleManualAction(action)} disabled={disabled} variant="secondary">
371
+ <Button key={action.id} type='button' size='sm' onClick={() => void handleManualAction(action)} disabled={disabled} variant='secondary'>
372
372
  {runningActionId === action.id ? t('connecting') : action.title}
373
373
  </Button>
374
374
  ))}
375
375
  </div>
376
- <Button type="submit" size="sm" disabled={disabled}>
376
+ <Button type='submit' size='sm' disabled={disabled}>
377
377
  {updateChannel.isPending ? t('saving') : t('save')}
378
378
  </Button>
379
379
  </FormActions>
@@ -395,8 +395,8 @@ export function ChannelForm({ channelName }: ChannelFormProps) {
395
395
  return (
396
396
  <ConfigSplitEmptyPane>
397
397
  <div>
398
- <h3 className="text-base font-semibold text-gray-900">{t('channelsSelectTitle')}</h3>
399
- <p className="mt-2 text-sm text-gray-500">{t('channelsSelectDescription')}</p>
398
+ <h3 className='text-base font-semibold text-foreground'>{t('channelsSelectTitle')}</h3>
399
+ <p className='mt-2 text-sm text-muted-foreground'>{t('channelsSelectDescription')}</p>
400
400
  </div>
401
401
  </ConfigSplitEmptyPane>
402
402
  );
@@ -1,8 +1,9 @@
1
1
  import { useEffect, useMemo, useState } from 'react';
2
2
  import { useQuery, useQueryClient } from '@tanstack/react-query';
3
3
  import { toDataURL } from 'qrcode';
4
- import { ExternalLink, Loader2, MessageCircleMore, QrCode } from 'lucide-react';
4
+ import { Loader2, MessageCircleMore, QrCode } from 'lucide-react';
5
5
  import { toast } from 'sonner';
6
+ import { NavigationLink } from '@/shared/components/actions/navigation-link';
6
7
  import { Button } from '@/shared/components/ui/button';
7
8
  import { Input } from '@/shared/components/ui/input';
8
9
  import { Label } from '@/shared/components/ui/label';
@@ -217,10 +218,9 @@ function QrAuthPanel({
217
218
  <p>{authMessage || activeSession.note || t(copy.scanPrompt)}</p>
218
219
  <p>{t('weixinAuthExpiresAt')}: {formatDateTime(activeSession.expiresAt)}</p>
219
220
  </div>
220
- <a href={activeSession.qrCodeUrl} target="_blank" rel="noreferrer" className="inline-flex items-center gap-1.5 text-xs text-primary transition-colors hover:text-primary-hover">
221
- <ExternalLink className="h-3.5 w-3.5" />
221
+ <NavigationLink href={activeSession.qrCodeUrl} external size="xs">
222
222
  {t('weixinAuthOpenQr')}
223
- </a>
223
+ </NavigationLink>
224
224
  </div>
225
225
  ) : (
226
226
  <div className="flex min-h-[280px] flex-col items-center justify-center rounded-2xl bg-gray-50/80 px-6 text-center">
@@ -402,10 +402,9 @@ function ExistingFeishuAgentConnectPanel({
402
402
  <h4 className="text-base font-semibold text-gray-900">{t('feishuExistingAgentDescription')}</h4>
403
403
  <p className="mt-1 text-sm text-gray-600">{t('feishuExistingAgentHint')}</p>
404
404
  </div>
405
- <a href={developerConsoleUrl} target="_blank" rel="noreferrer" className="inline-flex items-center gap-1.5 text-sm text-primary transition-colors hover:text-primary-hover">
406
- <ExternalLink className="h-4 w-4" />
405
+ <NavigationLink href={developerConsoleUrl} external>
407
406
  {platform === 'lark' ? t('feishuExistingAgentOpenLarkList') : t('feishuExistingAgentOpenFeishuList')}
408
- </a>
407
+ </NavigationLink>
409
408
  </div>
410
409
  <div className="w-full max-w-sm space-y-4">
411
410
  <div className="space-y-2">
@@ -219,13 +219,13 @@ describe('ChannelsList', () => {
219
219
 
220
220
  const scrollRegions = Array.from(container.querySelectorAll('div')).filter((node) => {
221
221
  const classTokens = node.className.split(/\s+/).filter(Boolean);
222
- return classTokens.includes('xl:overflow-y-auto');
222
+ return classTokens.includes('md:overflow-y-auto');
223
223
  });
224
224
 
225
225
  expect(scrollRegions.length).toBeGreaterThanOrEqual(2);
226
226
  scrollRegions.forEach((region) => {
227
227
  const classTokens = region.className.split(/\s+/).filter(Boolean);
228
- expect(classTokens).toContain('xl:overscroll-contain');
228
+ expect(classTokens).toContain('md:overscroll-contain');
229
229
  expect(classTokens).not.toContain('overflow-y-auto');
230
230
  expect(classTokens).not.toContain('overscroll-contain');
231
231
  });
@@ -1,7 +1,7 @@
1
1
  import { useMemo, useState } from 'react';
2
2
  import { ExternalLink, MessageSquare, Search } from 'lucide-react';
3
3
  import { LogoBadge } from '@/shared/components/common/logo-badge';
4
- import { PageHeader, PageLayout } from '@/app/components/layout/page-layout';
4
+ import { SettingsPage } from '@/shared/components/settings/settings-page';
5
5
  import { Input } from '@/shared/components/ui/input';
6
6
  import { StatusDot } from '@/shared/components/status/status-dot';
7
7
  import { Tabs, TabsList, TabsTrigger } from '@/shared/components/ui/tabs';
@@ -71,46 +71,47 @@ export function ChannelsList() {
71
71
  : filteredChannels[0]?.name ?? null;
72
72
 
73
73
  if (!config || !meta) {
74
- return <div className="p-8 text-gray-400">{t('channelsLoading')}</div>;
74
+ return (
75
+ <SettingsPage title={t('channelsPageTitle')} description={t('channelsPageDescription')} layout='split'>
76
+ <div className='text-sm text-muted-foreground'>{t('channelsLoading')}</div>
77
+ </SettingsPage>
78
+ );
75
79
  }
76
80
 
77
81
  return (
78
- <PageLayout className="pb-0 xl:flex xl:h-full xl:min-h-0 xl:flex-col">
79
- <PageHeader title={t('channelsPageTitle')} description={t('channelsPageDescription')} />
82
+ <SettingsPage title={t('channelsPageTitle')} description={t('channelsPageDescription')} layout='split'>
80
83
  <ConfigSplitPage
81
- className="xl:min-h-0"
82
- mobileView={isMobile ? (resolvedSelectedChannel ? 'detail' : 'list') : undefined}
84
+ className='md:min-h-0'
85
+ compactView={selectedChannel ? 'detail' : 'list'}
83
86
  onMobileBack={() => setSelectedChannel(null)}
84
87
  mobileListLabel={t('channelsPageTitle')}
85
88
  >
86
89
  <ConfigSplitSidebar>
87
- <ConfigSplitPaneHeader className="px-4 pt-4">
90
+ <ConfigSplitPaneHeader className='px-4 pt-4'>
88
91
  <Tabs value={activeTab} onValueChange={setActiveTab}>
89
- <TabsList className="mb-0 h-auto gap-3 border-b-0 bg-transparent p-0">
90
- <TabsTrigger value="enabled" className="gap-1.5 px-0 pb-3">
92
+ <TabsList className='mb-0 h-auto gap-3 border-b-0 bg-transparent p-0'>
93
+ <TabsTrigger value='enabled' className='gap-1.5 px-0 pb-3'>
91
94
  {t('channelsTabEnabled')}
92
- <span className="text-[11px] font-medium text-gray-500">
95
+ <span className='text-[11px] font-medium text-gray-500'>
93
96
  {channels.filter((channel) => channelConfigs?.[channel.name]?.enabled).length}
94
97
  </span>
95
98
  </TabsTrigger>
96
- <TabsTrigger value="all" className="gap-1.5 px-0 pb-3">
99
+ <TabsTrigger value='all' className='gap-1.5 px-0 pb-3'>
97
100
  {t('channelsTabAll')}
98
- <span className="text-[11px] font-medium text-gray-500">
99
- {channels.length}
100
- </span>
101
+ <span className='text-[11px] font-medium text-gray-500'>{channels.length}</span>
101
102
  </TabsTrigger>
102
103
  </TabsList>
103
104
  </Tabs>
104
105
  </ConfigSplitPaneHeader>
105
106
 
106
- <div className="border-b border-gray-100 px-4 py-3">
107
- <div className="relative">
108
- <Search className="pointer-events-none absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-gray-400" />
109
- <Input value={query} onChange={(event) => setQuery(event.target.value)} placeholder={t('channelsFilterPlaceholder')} className="h-10 rounded-xl pl-9" />
107
+ <div className='border-b border-border/55 px-4 py-3'>
108
+ <div className='relative'>
109
+ <Search className='pointer-events-none absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground/70' />
110
+ <Input value={query} onChange={(event) => setQuery(event.target.value)} placeholder={t('channelsFilterPlaceholder')} className='h-10 rounded-xl pl-9' />
110
111
  </div>
111
112
  </div>
112
113
 
113
- <ConfigSplitPaneBody className="space-y-2 p-3">
114
+ <ConfigSplitPaneBody className='space-y-2 p-3'>
114
115
  {filteredChannels.map((channel) => {
115
116
  const enabled = Boolean(config.channels[channel.name]?.enabled);
116
117
  const tutorialUrl = resolveChannelTutorialUrl(channel);
@@ -118,32 +119,32 @@ export function ChannelsList() {
118
119
 
119
120
  return (
120
121
  <ConfigSelectionCard key={channel.name} onClick={() => setSelectedChannel(channel.name)} active={resolvedSelectedChannel === channel.name}>
121
- <div className="flex items-start justify-between gap-3">
122
- <div className="flex min-w-0 items-center gap-3">
122
+ <div className='flex items-start justify-between gap-3'>
123
+ <div className='flex min-w-0 items-center gap-3'>
123
124
  <LogoBadge
124
125
  name={channel.name}
125
126
  src={getChannelLogo(channel.name)}
126
- className={cn('h-10 w-10 rounded-lg border', enabled ? 'border-primary/30 bg-white' : 'border-gray-200/70 bg-white')}
127
- imgClassName="h-5 w-5 object-contain"
128
- fallback={<span className="text-sm font-semibold uppercase text-gray-500">{channel.name[0]}</span>}
127
+ className={cn('h-10 w-10 rounded-lg bg-background/80', enabled && 'ring-1 ring-primary/20')}
128
+ imgClassName='h-5 w-5 object-contain'
129
+ fallback={<span className='text-sm font-semibold uppercase text-muted-foreground'>{channel.name[0]}</span>}
129
130
  />
130
- <div className="min-w-0">
131
- <p className="truncate text-sm font-semibold text-gray-900">{channel.displayName || channel.name}</p>
132
- <p className="line-clamp-1 text-[11px] text-gray-500">{description}</p>
131
+ <div className='min-w-0'>
132
+ <p className='truncate text-sm font-semibold text-foreground'>{channel.displayName || channel.name}</p>
133
+ <p className='line-clamp-1 text-[11px] text-muted-foreground'>{description}</p>
133
134
  </div>
134
135
  </div>
135
- <div className="flex items-center gap-2">
136
+ <div className='flex items-center gap-2'>
136
137
  {tutorialUrl ? (
137
138
  <a
138
139
  href={tutorialUrl}
139
140
  onClick={(event) => event.stopPropagation()}
140
- className="inline-flex h-7 w-7 items-center justify-center rounded-md text-gray-300 transition-colors hover:bg-gray-100/70 hover:text-gray-500"
141
+ className='inline-flex h-7 w-7 items-center justify-center rounded-md text-muted-foreground/45 transition-colors hover:bg-muted hover:text-foreground'
141
142
  title={t('channelsGuideTitle')}
142
143
  >
143
- <ExternalLink className="h-3.5 w-3.5" />
144
+ <ExternalLink className='h-3.5 w-3.5' />
144
145
  </a>
145
146
  ) : null}
146
- <StatusDot status={enabled ? 'active' : 'inactive'} label={enabled ? t('statusActive') : t('statusInactive')} className="min-w-[56px] justify-center" />
147
+ <StatusDot status={enabled ? 'active' : 'inactive'} label={enabled ? t('statusActive') : t('statusInactive')} className='min-w-[56px] justify-center' />
147
148
  </div>
148
149
  </div>
149
150
  </ConfigSelectionCard>
@@ -155,6 +156,6 @@ export function ChannelsList() {
155
156
 
156
157
  <ChannelForm channelName={resolvedSelectedChannel ?? undefined} />
157
158
  </ConfigSplitPage>
158
- </PageLayout>
159
+ </SettingsPage>
159
160
  );
160
161
  }
@@ -1,4 +1,4 @@
1
- import { render, screen } from "@testing-library/react";
1
+ import { fireEvent, render, screen, waitFor } from "@testing-library/react";
2
2
  import userEvent from "@testing-library/user-event";
3
3
  import type { NcpMessage } from "@nextclaw/ncp";
4
4
  import { beforeEach, expect, it, vi } from "vitest";
@@ -7,6 +7,7 @@ import { useChatMessageLayoutStore } from "@/features/chat/stores/chat-message-l
7
7
 
8
8
  const captures = vi.hoisted(() => ({
9
9
  isAtBottom: true,
10
+ messageListSessionKeys: [] as Array<string | null>,
10
11
  onScroll: vi.fn(),
11
12
  scrollToBottom: vi.fn(),
12
13
  }));
@@ -19,9 +20,19 @@ vi.mock("@nextclaw/agent-chat-ui", () => ({
19
20
  }),
20
21
  }));
21
22
 
22
- vi.mock("@/features/chat/features/message/components/chat-message-list.container", () => ({
23
- ChatMessageListContainer: () => <div data-testid="chat-message-list" />,
24
- }));
23
+ vi.mock(
24
+ "@/features/chat/features/message/components/chat-message-list.container",
25
+ () => ({
26
+ ChatMessageListContainer: ({
27
+ sessionKey,
28
+ }: {
29
+ sessionKey: string | null;
30
+ }) => {
31
+ captures.messageListSessionKeys.push(sessionKey);
32
+ return <div data-testid="chat-message-list" />;
33
+ },
34
+ }),
35
+ );
25
36
 
26
37
  vi.mock("@/shared/lib/i18n", () => ({
27
38
  t: (key: string) => key,
@@ -38,16 +49,27 @@ const messages = [
38
49
  },
39
50
  ] as unknown as readonly NcpMessage[];
40
51
 
41
- function renderContent(options: { bottomSlot?: React.ReactNode } = {}) {
52
+ function renderContent(
53
+ options: {
54
+ bottomSlot?: React.ReactNode;
55
+ hasPreviousMessages?: boolean;
56
+ historyError?: Error | null;
57
+ onLoadPreviousMessages?: () => Promise<void>;
58
+ } = {},
59
+ ) {
42
60
  return render(
43
61
  <ChatConversationContent
44
62
  bottomSlot={options.bottomSlot}
63
+ hasPreviousMessages={options.hasPreviousMessages ?? false}
64
+ historyError={options.historyError ?? null}
45
65
  isAwaitingAssistantOutput={false}
46
66
  isHistoryLoading={false}
67
+ isLoadingPreviousMessages={false}
47
68
  isSending={false}
48
69
  messages={messages}
49
70
  sessionKey="session-1"
50
71
  showWelcome={false}
72
+ onLoadPreviousMessages={options.onLoadPreviousMessages ?? vi.fn()}
51
73
  />,
52
74
  );
53
75
  }
@@ -55,6 +77,7 @@ function renderContent(options: { bottomSlot?: React.ReactNode } = {}) {
55
77
  beforeEach(() => {
56
78
  useChatMessageLayoutStore.getState().setLayout("card");
57
79
  captures.isAtBottom = true;
80
+ captures.messageListSessionKeys = [];
58
81
  captures.onScroll.mockReset();
59
82
  captures.scrollToBottom.mockReset();
60
83
  });
@@ -68,12 +91,15 @@ it("uses the centered reading track in flat message layout", () => {
68
91
  .getByTestId("chat-message-list")
69
92
  .closest('[data-chat-conversation-track="flat"]');
70
93
  expect(track?.className).toContain("max-w-[min(52rem,100%)]");
94
+ expect(captures.messageListSessionKeys).toEqual(["session-1"]);
71
95
  });
72
96
 
73
97
  it("hides the scroll-to-bottom action while the conversation is already at the bottom", () => {
74
98
  renderContent();
75
99
 
76
- expect(screen.queryByRole("button", { name: "chatScrollToBottom" })).toBeNull();
100
+ expect(
101
+ screen.queryByRole("button", { name: "chatScrollToBottom" }),
102
+ ).toBeNull();
77
103
  });
78
104
 
79
105
  it("scrolls to the latest message when the user clicks the floating action", async () => {
@@ -94,6 +120,41 @@ it("renders bottom slot after the message list", () => {
94
120
  const messageList = screen.getByTestId("chat-message-list");
95
121
  const bottomSlot = screen.getByTestId("conversation-bottom-slot");
96
122
  expect(
97
- messageList.compareDocumentPosition(bottomSlot) & Node.DOCUMENT_POSITION_FOLLOWING,
123
+ messageList.compareDocumentPosition(bottomSlot) &
124
+ Node.DOCUMENT_POSITION_FOLLOWING,
98
125
  ).toBeTruthy();
99
126
  });
127
+
128
+ it("loads older messages when the reader approaches the top", async () => {
129
+ const onLoadPreviousMessages = vi.fn().mockResolvedValue(undefined);
130
+ renderContent({ hasPreviousMessages: true, onLoadPreviousMessages });
131
+ const scrollContainer = document.querySelector<HTMLElement>(
132
+ '[data-chat-scroll-container="true"]',
133
+ );
134
+ if (!scrollContainer) {
135
+ throw new Error("Expected chat scroll container");
136
+ }
137
+ scrollContainer.scrollTop = 200;
138
+
139
+ fireEvent.scroll(scrollContainer);
140
+
141
+ await waitFor(() => expect(onLoadPreviousMessages).toHaveBeenCalledOnce());
142
+ expect(captures.onScroll).toHaveBeenCalledOnce();
143
+ });
144
+
145
+ it("shows a retry action when loading earlier messages fails", async () => {
146
+ const user = userEvent.setup();
147
+ const onLoadPreviousMessages = vi.fn().mockResolvedValue(undefined);
148
+ renderContent({
149
+ hasPreviousMessages: true,
150
+ historyError: new Error("network unavailable"),
151
+ onLoadPreviousMessages,
152
+ });
153
+
154
+ expect(screen.getByRole("alert").textContent).toContain(
155
+ "chatHistoryLoadFailed",
156
+ );
157
+ await user.click(screen.getByRole("button", { name: "chatHistoryRetry" }));
158
+
159
+ expect(onLoadPreviousMessages).toHaveBeenCalledOnce();
160
+ });
@@ -13,6 +13,7 @@ import type { NcpSessionSummaryView, SessionEntryView } from "@/shared/lib/api";
13
13
 
14
14
  const mocks = vi.hoisted(() => ({
15
15
  deleteSession: vi.fn(),
16
+ createSession: vi.fn(),
16
17
  toggleWorkspacePanel: vi.fn(),
17
18
  selectSession: vi.fn(),
18
19
  setListMode: vi.fn(),
@@ -28,6 +29,7 @@ vi.mock("@/features/chat/components/providers/chat-presenter.provider", () => ({
28
29
  toggleWorkspacePanel: mocks.toggleWorkspacePanel,
29
30
  },
30
31
  chatSessionListManager: {
32
+ createSession: mocks.createSession,
31
33
  selectSession: mocks.selectSession,
32
34
  setListMode: mocks.setListMode,
33
35
  toggleProjectCollapsed: mocks.toggleProjectCollapsed,
@@ -35,6 +37,15 @@ vi.mock("@/features/chat/components/providers/chat-presenter.provider", () => ({
35
37
  }),
36
38
  }));
37
39
 
40
+ vi.mock("@/features/chat/features/session-type/hooks/use-chat-new-session-type-preference", () => ({
41
+ useChatNewSessionTypePreference: () => ({
42
+ selectedSessionType: "codex",
43
+ selectedSessionTypeOption: null,
44
+ isLoading: false,
45
+ setSelectedSessionType: vi.fn(),
46
+ }),
47
+ }));
48
+
38
49
  vi.mock("@/features/chat/features/ncp/hooks/use-ncp-session-list-view", () => ({
39
50
  useNcpSessionListView: (params?: { query?: string | null }) => {
40
51
  const query = params?.query?.trim().toLowerCase() ?? "";
@@ -122,6 +133,7 @@ function renderHeaderSection(layoutMode: "desktop" | "mobile" = "desktop") {
122
133
  describe("ChatConversationHeaderSection", () => {
123
134
  beforeEach(() => {
124
135
  mocks.deleteSession.mockReset();
136
+ mocks.createSession.mockReset();
125
137
  mocks.toggleWorkspacePanel.mockReset();
126
138
  mocks.selectSession.mockReset();
127
139
  mocks.setListMode.mockReset();
@@ -261,6 +273,21 @@ describe("ChatConversationHeaderSection", () => {
261
273
  expect(mocks.selectSession).toHaveBeenCalledWith("session:ncp-2");
262
274
  });
263
275
 
276
+ it("starts a new preferred session from the mobile conversation header", async () => {
277
+ const user = userEvent.setup();
278
+
279
+ renderHeaderSection("mobile");
280
+ await user.click(screen.getByRole("button", { name: "New Task" }));
281
+
282
+ expect(mocks.createSession).toHaveBeenCalledWith("codex");
283
+ });
284
+
285
+ it("keeps the new-session shortcut mobile-only", () => {
286
+ renderHeaderSection();
287
+
288
+ expect(screen.queryByRole("button", { name: "New Task" })).toBeNull();
289
+ });
290
+
264
291
  it("uses lightweight static groups for pinned and activity dates", async () => {
265
292
  const user = userEvent.setup();
266
293
  viewportLayoutManager.setSidebarCollapsed(true);
@@ -22,10 +22,6 @@ function createPersistStorage() {
22
22
  };
23
23
  }
24
24
 
25
- vi.mock("@/features/chat/features/ncp/hooks/use-ncp-chat-derived-state", () => ({
26
- useNcpChatProviderStateResolved: () => true,
27
- }));
28
-
29
25
  vi.mock("@/features/chat/components/providers/chat-presenter.provider", () => ({
30
26
  usePresenter: () => ({
31
27
  chatUiManager: {
@@ -43,10 +39,6 @@ vi.mock("@/features/chat/components/conversation/chat-conversation-header-sectio
43
39
  ChatConversationHeaderSection: () => <div data-testid="chat-header" />,
44
40
  }));
45
41
 
46
- vi.mock("@/features/chat/components/conversation/chat-conversation-skeleton", () => ({
47
- ChatConversationSkeleton: () => <div data-testid="chat-skeleton" />,
48
- }));
49
-
50
42
  vi.mock("@/features/chat/components/conversation/chat-conversation-workspace-section", () => ({
51
43
  ChatConversationWorkspaceSection: ({ sessionKey }: { sessionKey: string | null }) => (
52
44
  <div data-testid="workspace-section" data-session-key={sessionKey ?? ""} />