@nextclaw/ui 0.15.4 → 0.15.6

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 (300) hide show
  1. package/CHANGELOG.md +41 -0
  2. package/dist/assets/abnfDiagram-VRR7QNED-BYpK0F5F.js +1 -0
  3. package/dist/assets/{aiden0z-pptx-renderer.es-Bd99vut2.js → aiden0z-pptx-renderer.es-D87ywFdC.js} +1 -1
  4. package/dist/assets/api-Dyb5rgh0.js +13 -0
  5. package/dist/assets/app-presenter-provider-7jV0nPiL.js +106 -0
  6. package/dist/assets/{appearance-settings-page-fLIJbx1C.js → appearance-settings-page-DFm3BbEb.js} +1 -1
  7. package/dist/assets/arc-DFELHWM2.js +1 -0
  8. package/dist/assets/architecture-TIHT7OUA-ClwVFq-K.js +1 -0
  9. package/dist/assets/architectureDiagram-ZJ3FMSHR-CvRhdovL.js +36 -0
  10. package/dist/assets/array-BqYdCIE6.js +1 -0
  11. package/dist/assets/blockDiagram-677ZJIJ3-DVYNkbR_.js +132 -0
  12. package/dist/assets/{book-open-DWUjS6aW.js → book-open-L6LCuqOr.js} +1 -1
  13. package/dist/assets/c4Diagram-LMCZKHZV-B1w-iwRe.js +10 -0
  14. package/dist/assets/channel-BEHOSFc5.js +1 -0
  15. package/dist/assets/{channels-list-page-CvCTJraa.js → channels-list-page-DOaPKNJA.js} +2 -2
  16. package/dist/assets/{chat-page-B42UUIFR.js → chat-page-Cw9uDhD_.js} +1 -1
  17. package/dist/assets/chunk-2Q5K7J3B-DrIhesSX.js +1 -0
  18. package/dist/assets/chunk-32BRIVSS-kG3lg0bC.js +1 -0
  19. package/dist/assets/chunk-52WLFC77-BLeB7iGk.js +10 -0
  20. package/dist/assets/chunk-5FCAYU7R-BQga1Rr7.js +1 -0
  21. package/dist/assets/chunk-5HE753X5-DU81hkcF.js +1 -0
  22. package/dist/assets/chunk-5JV3BV7I-C2bAx0m7.js +1 -0
  23. package/dist/assets/chunk-5TONJI2A-BExIIgWY.js +2 -0
  24. package/dist/assets/chunk-5VM5RSS4-C3XB05yh.js +15 -0
  25. package/dist/assets/chunk-7BUUIJ7U-JiyRtrGu.js +1 -0
  26. package/dist/assets/chunk-BIQX33UG-BZRV20pI.js +1 -0
  27. package/dist/assets/chunk-C7G6YPKG-AsyDSQ9j.js +1 -0
  28. package/dist/assets/chunk-CQNSW5MT-iN-fiXVA.js +1 -0
  29. package/dist/assets/chunk-CYSBUYHQ-DhC_TRzI.js +1 -0
  30. package/dist/assets/chunk-EMLP6XTP-QFLK8R6d.js +1 -0
  31. package/dist/assets/chunk-EX3LRPZG-DzUwhkCz.js +231 -0
  32. package/dist/assets/chunk-FWX5IMBZ-DFelpZBy.js +2 -0
  33. package/dist/assets/chunk-HOUHSVGY-BMPwFEwN.js +1 -0
  34. package/dist/assets/chunk-ICXQ74PX-DH8NJMok.js +2 -0
  35. package/dist/assets/chunk-JG7HCLWE-D-95AsO4.js +2 -0
  36. package/dist/assets/chunk-JWPE2WC7-DIaTv8kx.js +1 -0
  37. package/dist/assets/chunk-KEIR6QF5-A3jEqwzn.js +161 -0
  38. package/dist/assets/chunk-MOJQB5TN-BPFLzWgj.js +88 -0
  39. package/dist/assets/chunk-MOZMSUNE-DR_fzxG0.js +1 -0
  40. package/dist/assets/chunk-OGEWGWER-TIcEoPPH.js +1 -0
  41. package/dist/assets/chunk-OSBZ3O6U-BALNvSfG.js +1 -0
  42. package/dist/assets/chunk-PUDLZKDR-I-EXhf_k.js +156 -0
  43. package/dist/assets/chunk-Q4XR5HBZ-uvIGstBS.js +70 -0
  44. package/dist/assets/chunk-QBLGF6JB-r-TjLdxa.js +1 -0
  45. package/dist/assets/chunk-R7FJI6CG-N-1LDgvy.js +1 -0
  46. package/dist/assets/chunk-RYQCIY6F-jxNASx65.js +1 -0
  47. package/dist/assets/chunk-U6XO7XAA-Cphbuc7q.js +2 -0
  48. package/dist/assets/chunk-V7JOEXUC-BLEuBf1s.js +206 -0
  49. package/dist/assets/chunk-VAUOI2AC-Dpcz6BhT.js +1 -0
  50. package/dist/assets/chunk-VR4S4FIN-BAUbR0ke.js +1 -0
  51. package/dist/assets/chunk-WYO6CB5R-Do6SJrnY.js +127 -0
  52. package/dist/assets/chunk-XXDRQBXY-C2QjjwYX.js +1 -0
  53. package/dist/assets/chunk-Y2CYZVJY-BGrd8tLy.js +1 -0
  54. package/dist/assets/chunk-YOTPTUD7-CQqvMTCr.js +1 -0
  55. package/dist/assets/chunk-ZGVPDNZ5-B7kWWaXv.js +62 -0
  56. package/dist/assets/chunk-ZIRB5QZD-DTU0cDNh.js +32 -0
  57. package/dist/assets/classDiagram-OUVF2IWQ-DESMaY3_.js +1 -0
  58. package/dist/assets/classDiagram-v2-EOCWNBFH-DwOlByYH.js +1 -0
  59. package/dist/assets/{config-split-page-DBgt9S8b.js → config-split-page-CqlqjnCK.js} +1 -1
  60. package/dist/assets/{confirm-dialog-GSXIvoRU.js → confirm-dialog-bAp8NU93.js} +1 -1
  61. package/dist/assets/cose-bilkent-JH36ORCC-B9LrtV-q.js +1 -0
  62. package/dist/assets/cynefin-VYW2F7L2-CzjeeOq9.js +1 -0
  63. package/dist/assets/cynefinDiagram-TSTJHNR4-BfjvW4su.js +62 -0
  64. package/dist/assets/cytoscape.esm-CRW8q92n.js +321 -0
  65. package/dist/assets/dagre-DkiXZKkL.js +1 -0
  66. package/dist/assets/dagre-VKFMJZFB-B1pQKQSY.js +4 -0
  67. package/dist/assets/defaultLocale-Cdv9x6q7.js +1 -0
  68. package/dist/assets/desktop-update-config-2SjMfCMl.js +1 -0
  69. package/dist/assets/diagram-FQU43EPY-cB4EZdaP.js +3 -0
  70. package/dist/assets/diagram-G47NLZAW-CtW8im1B.js +24 -0
  71. package/dist/assets/diagram-NH7WQ7WH-BDCtrCCm.js +24 -0
  72. package/dist/assets/diagram-OA4YK3LP-DEMJ42Gt.js +30 -0
  73. package/dist/assets/diagram-WEI45ONY-CdCcxSYU.js +41 -0
  74. package/dist/assets/{dist-DC7f4TJH.js → dist-CrtbGLlh.js} +1 -1
  75. package/dist/assets/dist-dw-gAfM_.js +1 -0
  76. package/dist/assets/doc-browser-A5IRyDXQ2.js +1 -0
  77. package/dist/assets/{doc-browser-D0monHjG.js → doc-browser-BmnQa30-.js} +1 -1
  78. package/dist/assets/{doc-browser-context-CGwEOhBy.js → doc-browser-context-BlpUUFrR.js} +1 -1
  79. package/dist/assets/{docx-preview-eFH9DRsS.js → docx-preview-zis3U0Fa.js} +1 -1
  80. package/dist/assets/{download-DiP9zx-J.js → download-BhZZNXD8.js} +1 -1
  81. package/dist/assets/ebnfDiagram-CCIWWBDH-DBZETBwE.js +1 -0
  82. package/dist/assets/erDiagram-Q63AITRT-y2zDXprP.js +85 -0
  83. package/dist/assets/eventmodeling-45OFAUF4-C38BqG8w.js +1 -0
  84. package/dist/assets/{external-link-C6ZeSPef.js → external-link-B9Uoq5J7.js} +1 -1
  85. package/dist/assets/{eye-suvKmtwn.js → eye-K5knauEM.js} +1 -1
  86. package/dist/assets/flowDiagram-23GEKE2U-BoKk8RRq.js +1 -0
  87. package/dist/assets/{folder-CMELbQGG.js → folder-BIYZZWJv.js} +1 -1
  88. package/dist/assets/{form-actions-CUAzpNe_.js → form-actions-CKprWFeM.js} +1 -1
  89. package/dist/assets/ganttDiagram-NO4QXBWP-D2DP344e.js +292 -0
  90. package/dist/assets/gitGraph-TEB2WS4Q-IsQHHJuS.js +1 -0
  91. package/dist/assets/gitGraphDiagram-IHSO6WYX-D37uiwA2.js +106 -0
  92. package/dist/assets/graphlib-BcNFQr5y.js +1 -0
  93. package/dist/assets/index-BVn702m4.css +1 -0
  94. package/dist/assets/index-CFxJ1Fkg.js +2 -0
  95. package/dist/assets/info-DKCQHKI2-ofy2qlHI.js +1 -0
  96. package/dist/assets/infoDiagram-FWYZ7A6U-CG-WAIRi.js +2 -0
  97. package/dist/assets/init-D8TjIqfm.js +1 -0
  98. package/dist/assets/ishikawaDiagram-FXEZZL3T-WeCTnW0f.js +70 -0
  99. package/dist/assets/journeyDiagram-5HDEW3XC-DUFBW9Td.js +139 -0
  100. package/dist/assets/kanban-definition-HUTT4EX6-B0lxgMpg.js +89 -0
  101. package/dist/assets/katex-wEmrEFTg.js +257 -0
  102. package/dist/assets/line-B78frP_c.js +1 -0
  103. package/dist/assets/linear-C6lVEcb2.js +1 -0
  104. package/dist/assets/loader-circle-Bj2RMrCR.js +1 -0
  105. package/dist/assets/{logo-badge-Cu5UGH_4.js → logo-badge-BQ-XFZod.js} +1 -1
  106. package/dist/assets/map-Bdri5UL5.js +1 -0
  107. package/dist/assets/mcp-marketplace-page-BfA7A3n4.js +9 -0
  108. package/dist/assets/mcp-marketplace-page-CXHYO95j.js +1 -0
  109. package/dist/assets/mermaid-parser.core-Cue18vLo.js +4 -0
  110. package/dist/assets/mermaid.core-CXwWRh5i.js +11 -0
  111. package/dist/assets/mindmap-definition-LN4V7U3C-CFxz0P0-.js +96 -0
  112. package/dist/assets/{model-config-page-CWP6pyBm.js → model-config-page-BbWbW_qM.js} +1 -1
  113. package/dist/assets/ordinal-Cbd8t4LB.js +1 -0
  114. package/dist/assets/packet-7NZHBO7P-Rfopfa1o.js +1 -0
  115. package/dist/assets/path-DzBnMI1r.js +1 -0
  116. package/dist/assets/pegDiagram-2B236MQR-B_Qt9zyT.js +1 -0
  117. package/dist/assets/pie-RZYD4A2V-CJR1xKEv.js +1 -0
  118. package/dist/assets/pieDiagram-ENE6RG2P-CRQdSuwS.js +39 -0
  119. package/dist/assets/plus-TmFdwO3P.js +1 -0
  120. package/dist/assets/{popover-DImguRok.js → popover-CX9zFsuc.js} +1 -1
  121. package/dist/assets/preload-helper-D4M6sveU.js +1 -0
  122. package/dist/assets/{provider-scoped-model-input-CE7p4SbJ.js → provider-scoped-model-input-B-9_DtIP.js} +1 -1
  123. package/dist/assets/providers-config-page-xwIjGur2.js +1 -0
  124. package/dist/assets/quadrantDiagram-ABIIQ3AL-D5kPpttq.js +7 -0
  125. package/dist/assets/radar-I7S5WNFK-CDg8Z-1r.js +1 -0
  126. package/dist/assets/railroad-3IZDKUUU-BHnNmjxO.js +1 -0
  127. package/dist/assets/railroad-abnf-AHOZXSZD-DGtNcusW.js +1 -0
  128. package/dist/assets/railroad-ebnf-EBAXGLYW-BRFtsQmm.js +1 -0
  129. package/dist/assets/railroad-peg-LSFZ7HO6-Bj5IypCA.js +1 -0
  130. package/dist/assets/railroadDiagram-RFXS5EU6-DGh_TGlQ.js +1 -0
  131. package/dist/assets/{react-5sjAT_6d.js → react-DsgudwpT.js} +1 -1
  132. package/dist/assets/{refresh-cw-CHLBzWWj.js → refresh-cw-kXdvbhFf.js} +1 -1
  133. package/dist/assets/remote-B9F2uCk9.js +1 -0
  134. package/dist/assets/requirementDiagram-TGXJPOKE-EmFph0H_.js +84 -0
  135. package/dist/assets/rotate-cw-DEc6aihr.js +1 -0
  136. package/dist/assets/rough.esm-wJ1d3nQl.js +1 -0
  137. package/dist/assets/runtime-config-page-DTsi7czJ.js +1 -0
  138. package/dist/assets/sankeyDiagram-HTMAVEWB-BXWB5Ugi.js +40 -0
  139. package/dist/assets/{save-BABzyrjB.js → save-C9qpz2_2.js} +1 -1
  140. package/dist/assets/{search-BTqxG3ZG.js → search-_kEi29qo.js} +1 -1
  141. package/dist/assets/{search-config-page-BkKKmxmS.js → search-config-page-H7ug8f4D.js} +1 -1
  142. package/dist/assets/{secrets-config-page-CWJMyu-k.js → secrets-config-page-C1_6rINu.js} +2 -2
  143. package/dist/assets/security-config-Cr2ERKLu.js +1 -0
  144. package/dist/assets/{select-TYZDBNmq.js → select-MJKhKAWR.js} +1 -1
  145. package/dist/assets/sequenceDiagram-DBY2YBRQ-ClKnO7v0.js +162 -0
  146. package/dist/assets/{setting-row-DR23Z_5I.js → setting-row-DA3tJuew.js} +1 -1
  147. package/dist/assets/{settings-2-D8WWcBnw.js → settings-2-D_y7zXJz.js} +1 -1
  148. package/dist/assets/sizeCapture-X5ZJPWSS-DSo-t5ou.js +1 -0
  149. package/dist/assets/{skeleton-CloHeg0S.js → skeleton-0uTLUhm9.js} +1 -1
  150. package/dist/assets/src-Dad2ZbCt.js +1 -0
  151. package/dist/assets/stateDiagram-2N3HPSRC-BvAalBfz.js +1 -0
  152. package/dist/assets/stateDiagram-v2-6OUMAXLB-CfMU38yc.js +1 -0
  153. package/dist/assets/swimlanes-5IMT3BWC-DdVqVa1a.js +2 -0
  154. package/dist/assets/swimlanesDiagram-G3AALYLV-D9Zb9X57.js +8 -0
  155. package/dist/assets/{tabs-CX-ckWoo.js → tabs-h44XPocd.js} +1 -1
  156. package/dist/assets/{tag-chip-mCc23Rhb.js → tag-chip-bRZ04q3C.js} +1 -1
  157. package/dist/assets/timeline-definition-FHXFAJF6-D4ar8IQY.js +120 -0
  158. package/dist/assets/{trash-2-SAW1Z4Po.js → trash-2-BRpSE-Wt.js} +1 -1
  159. package/dist/assets/treeView-QDETBFTQ-Uy7M0WvH.js +1 -0
  160. package/dist/assets/treemap-6X3UGDF4-BJfGV7KX.js +1 -0
  161. package/dist/assets/use-config-CwDEAl6f.js +1 -0
  162. package/dist/assets/{use-confirm-dialog-C95F6sT1.js → use-confirm-dialog-BbnWw-8c.js} +1 -1
  163. package/dist/assets/{use-viewport-layout-C6m3Nte7.js → use-viewport-layout-CqgIMDQL.js} +1 -1
  164. package/dist/assets/vennDiagram-L72KCM5P-CDXJ_i3p.js +34 -0
  165. package/dist/assets/wardley-OPB4EBWU-q263iF3K.js +1 -0
  166. package/dist/assets/wardleyDiagram-EHGQE667-D-dSuT_7.js +78 -0
  167. package/dist/assets/x-DdEDL6ju.js +1 -0
  168. package/dist/assets/xychartDiagram-FW5EYKEG-C9JCzsZF.js +7 -0
  169. package/dist/index.html +33 -32
  170. package/package.json +8 -8
  171. package/src/features/chat/components/chat-popover-content.tsx +1 -26
  172. package/src/features/chat/components/conversation/__tests__/chat-conversation-header-section.test.tsx +84 -1
  173. package/src/features/chat/components/conversation/chat-conversation-header-section.tsx +13 -2
  174. package/src/features/chat/components/conversation/chat-conversation-workspace-section.tsx +1 -0
  175. package/src/features/chat/components/layout/__tests__/chat-sidebar-test.utils.ts +22 -0
  176. package/src/features/chat/components/layout/__tests__/chat-sidebar.test.tsx +13 -24
  177. package/src/features/chat/components/layout/chat-sidebar-desktop-layout.tsx +16 -4
  178. package/src/features/chat/components/layout/chat-sidebar.tsx +38 -2
  179. package/src/features/chat/features/conversation/components/__tests__/session-conversation-area.test.tsx +1 -0
  180. package/src/features/chat/features/conversation/components/__tests__/session-conversation-input.streaming.test.tsx +52 -2
  181. package/src/features/chat/features/conversation/components/session-conversation-area.tsx +22 -0
  182. package/src/features/chat/features/conversation/components/session-conversation-input.tsx +36 -20
  183. package/src/features/chat/features/conversation/hooks/__tests__/use-session-conversation-input-state.test.tsx +55 -0
  184. package/src/features/chat/features/conversation/hooks/use-session-conversation-input-state.ts +12 -11
  185. package/src/features/chat/features/conversation/hooks/use-session-conversation-preference-actions.ts +94 -0
  186. package/src/features/chat/features/input/hooks/__tests__/use-chat-input-surface-state.test.tsx +37 -0
  187. package/src/features/chat/features/input/hooks/use-chat-input-surface-state.ts +140 -63
  188. package/src/features/chat/features/input/input-surface-plugins/__tests__/context-reference-plugin.test.ts +175 -0
  189. package/src/features/chat/features/input/input-surface-plugins/__tests__/slash-command-plugin.test.ts +66 -0
  190. package/src/features/chat/features/input/input-surface-plugins/chat-input-product-plugin-adapters.types.ts +8 -1
  191. package/src/features/chat/features/input/input-surface-plugins/context-reference-plugin.utils.ts +274 -0
  192. package/src/features/chat/features/input/input-surface-plugins/slash-command-plugin.utils.ts +18 -11
  193. package/src/features/chat/features/input/utils/__tests__/chat-composer-state.utils.test.ts +26 -0
  194. package/src/features/chat/features/input/utils/__tests__/chat-inline-token.utils.test.ts +50 -4
  195. package/src/features/chat/features/input/utils/__tests__/chat-input-bar.utils.test.ts +51 -0
  196. package/src/features/chat/features/input/utils/chat-composer-state.utils.ts +4 -10
  197. package/src/features/chat/features/input/utils/chat-composer-token-protocol.utils.ts +29 -0
  198. package/src/features/chat/features/input/utils/chat-inline-token.utils.ts +66 -8
  199. package/src/features/chat/features/input/utils/chat-input-bar.utils.ts +62 -21
  200. package/src/features/chat/features/message/components/__tests__/chat-message-list.container.test.tsx +42 -10
  201. package/src/features/chat/features/message/components/chat-inline-file-preview.tsx +80 -0
  202. package/src/features/chat/features/message/components/chat-message-list.container.tsx +55 -60
  203. package/src/features/chat/features/message/utils/chat-message-texts.utils.ts +2 -0
  204. package/src/features/chat/features/ncp/hooks/__tests__/use-hydrated-ncp-agent.test.tsx +41 -3
  205. package/src/features/chat/features/project/components/__tests__/chat-project-create-dialog.test.tsx +67 -0
  206. package/src/features/chat/features/project/components/chat-project-create-dialog.tsx +203 -0
  207. package/src/features/chat/features/session/components/session-header/__tests__/chat-session-header-actions.test.tsx +7 -7
  208. package/src/features/chat/features/session/components/session-header/__tests__/chat-session-project-dialog.test.tsx +47 -0
  209. package/src/features/chat/features/session/components/session-header/chat-session-header-actions.tsx +6 -3
  210. package/src/features/chat/features/session/components/session-header/chat-session-project-badge.tsx +3 -0
  211. package/src/features/chat/features/session/components/session-header/chat-session-project-dialog.tsx +3 -0
  212. package/src/features/chat/features/session/components/session-header/chat-session-switcher-groups.tsx +245 -0
  213. package/src/features/chat/features/session/components/session-header/chat-session-title-switcher.tsx +81 -127
  214. package/src/features/chat/features/session/utils/__tests__/chat-sidebar-session-groups.utils.test.ts +22 -0
  215. package/src/features/chat/features/session/utils/chat-run-metadata.utils.ts +6 -6
  216. package/src/features/chat/features/session/utils/chat-sidebar-session-groups.utils.ts +12 -1
  217. package/src/features/chat/features/welcome/components/chat-conversation-welcome.tsx +3 -0
  218. package/src/features/chat/features/welcome/components/chat-welcome.tsx +1 -0
  219. package/src/features/chat/features/welcome/utils/__tests__/chat-welcome-project-options.utils.test.ts +20 -0
  220. package/src/features/chat/features/welcome/utils/chat-welcome-project-options.utils.ts +15 -2
  221. package/src/features/chat/features/workspace/components/__tests__/chat-session-workspace-file-preview.test.tsx +22 -24
  222. package/src/features/chat/features/workspace/components/__tests__/chat-session-workspace-panel.test.tsx +2 -1
  223. package/src/features/chat/features/workspace/components/chat-session-workspace-file-breadcrumbs.tsx +6 -17
  224. package/src/features/chat/features/workspace/components/chat-session-workspace-file-preview.tsx +41 -19
  225. package/src/features/chat/features/workspace/components/chat-session-workspace-panel-nav.tsx +5 -1
  226. package/src/features/chat/features/workspace/components/chat-session-workspace-panel.tsx +5 -1
  227. package/src/features/chat/features/workspace/utils/__tests__/chat-workspace-panel-view-model.utils.test.ts +104 -3
  228. package/src/features/chat/features/workspace/utils/chat-thread-workspace-session.utils.ts +83 -0
  229. package/src/features/chat/features/workspace/utils/chat-workspace-file-tab.utils.ts +4 -1
  230. package/src/features/chat/features/workspace/utils/chat-workspace-panel-view-model.utils.ts +96 -25
  231. package/src/features/chat/managers/__tests__/chat-thread-workspace.manager.test.ts +25 -0
  232. package/src/features/chat/managers/__tests__/chat-thread.manager.test.ts +5 -1
  233. package/src/features/chat/managers/chat-session-list.manager.ts +1 -0
  234. package/src/features/chat/managers/chat-thread.manager.ts +15 -37
  235. package/src/features/chat/pages/__tests__/ncp-chat-page.test.ts +23 -0
  236. package/src/features/chat/stores/__tests__/chat-thread.store.test.ts +9 -0
  237. package/src/features/chat/stores/chat-thread.store.ts +16 -36
  238. package/src/features/chat/types/chat-input-bar.types.ts +2 -0
  239. package/src/features/settings/components/config/provider-form.tsx +0 -4
  240. package/src/features/settings/utils/__tests__/provider-form-support.utils.test.ts +7 -9
  241. package/src/features/settings/utils/provider-form-model.utils.ts +0 -4
  242. package/src/features/system-status/hooks/use-current-version-release-notes-link.ts +33 -0
  243. package/src/features/system-status/index.ts +9 -0
  244. package/src/features/system-status/utils/update-release-notes.utils.ts +44 -3
  245. package/src/index.css +10 -16
  246. package/src/shared/components/common/__tests__/brand-header.test.tsx +127 -10
  247. package/src/shared/components/common/brand-header.tsx +142 -56
  248. package/src/shared/components/path-picker/__tests__/server-path-picker-dialog.test.tsx +288 -38
  249. package/src/shared/components/path-picker/server-path-picker-dialog.tsx +290 -199
  250. package/src/shared/components/path-picker/server-path-picker-directory-list.tsx +103 -0
  251. package/src/shared/components/path-picker/server-path-picker-footer.tsx +58 -0
  252. package/src/shared/components/path-picker/server-path-picker-locations.tsx +148 -0
  253. package/src/shared/components/path-picker/server-path-picker-navigation-bar.tsx +198 -0
  254. package/src/shared/components/path-picker/server-path-picker-new-folder.tsx +59 -0
  255. package/src/shared/components/path-picker/server-path-picker-toolbar.tsx +25 -0
  256. package/src/shared/hooks/use-projects.ts +28 -0
  257. package/src/shared/hooks/use-server-path-create-directory.ts +6 -0
  258. package/src/shared/hooks/use-server-path-read.ts +10 -5
  259. package/src/shared/hooks/use-server-path-search.ts +40 -0
  260. package/src/shared/lib/api/index.ts +1 -0
  261. package/src/shared/lib/api/projects/project.utils.ts +14 -0
  262. package/src/shared/lib/api/types.ts +30 -3
  263. package/src/shared/lib/api/utils/server-path.utils.ts +22 -1
  264. package/src/shared/lib/i18n/locales/en-US/chat.json +43 -2
  265. package/src/shared/lib/i18n/locales/en-US/core.json +2 -3
  266. package/src/shared/lib/i18n/locales/en-US/desktop-update.json +4 -0
  267. package/src/shared/lib/i18n/locales/en-US/path-picker.json +25 -0
  268. package/src/shared/lib/i18n/locales/zh-CN/chat.json +43 -2
  269. package/src/shared/lib/i18n/locales/zh-CN/core.json +2 -3
  270. package/src/shared/lib/i18n/locales/zh-CN/desktop-update.json +4 -0
  271. package/src/shared/lib/i18n/locales/zh-CN/path-picker.json +25 -0
  272. package/src/shared/lib/session-project/__tests__/workspace-file-breadcrumb.test.ts +1 -4
  273. package/src/shared/lib/session-project/workspace-file-breadcrumb.utils.ts +1 -18
  274. package/dist/assets/api-BOXOrpcJ.js +0 -13
  275. package/dist/assets/app-presenter-provider-CkHLbvZQ.js +0 -106
  276. package/dist/assets/desktop-update-config-7bmNeWIC.js +0 -1
  277. package/dist/assets/doc-browser-CgdoLUT92.js +0 -1
  278. package/dist/assets/index-BFeNDQ4R.css +0 -1
  279. package/dist/assets/index-D_6U-M94.js +0 -2
  280. package/dist/assets/loader-circle-tYHStefV.js +0 -1
  281. package/dist/assets/mcp-marketplace-page-BUzHDwW6.js +0 -9
  282. package/dist/assets/mcp-marketplace-page-ohUAdfqJ.js +0 -1
  283. package/dist/assets/plus-tywizkeY.js +0 -1
  284. package/dist/assets/providers-config-page-BKPo8SjH.js +0 -1
  285. package/dist/assets/remote-DXJryUxB.js +0 -1
  286. package/dist/assets/rotate-cw-BefO5brU.js +0 -1
  287. package/dist/assets/runtime-config-page-1Axv4QGu.js +0 -1
  288. package/dist/assets/security-config-DeQUIXfi.js +0 -1
  289. package/dist/assets/use-config-BucdaK23.js +0 -1
  290. package/dist/assets/x-DtCPHwCu.js +0 -1
  291. package/src/features/chat/components/__tests__/chat-popover-content.test.tsx +0 -41
  292. package/src/features/chat/components/layout/__tests__/chat-sidebar-utility-menu.test.tsx +0 -55
  293. package/src/features/chat/features/input/input-surface-plugins/__tests__/panel-app-reference-plugin.test.ts +0 -77
  294. package/src/features/chat/features/input/input-surface-plugins/panel-app-reference-plugin.utils.ts +0 -144
  295. /package/dist/assets/{config-hints-fDrYfl0l.js → config-hints-LxG80Uj5.js} +0 -0
  296. /package/dist/assets/{host-capabilities-zZvY9U63.js → host-capabilities-CFbAfrrA.js} +0 -0
  297. /package/dist/assets/{jszip.min-Das2IAnb.js → jszip.min-CP5uPNQa.js} +0 -0
  298. /package/dist/assets/{middleware-BLMHv9YF.js → middleware-CAXxl-af.js} +0 -0
  299. /package/dist/assets/{provider-models-C_yOh6DE.js → provider-models-BoUhFo9R.js} +0 -0
  300. /package/dist/assets/{xlsx-B6zVt1RK.js → xlsx-DUqJhg5u.js} +0 -0
@@ -1,5 +1,5 @@
1
1
  import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
2
- import { render, screen } from "@testing-library/react";
2
+ import { render, screen, within } from "@testing-library/react";
3
3
  import userEvent from "@testing-library/user-event";
4
4
  import type { PersistStorage, StorageValue } from "zustand/middleware";
5
5
  import { beforeEach, describe, expect, it, vi } from "vitest";
@@ -15,6 +15,8 @@ const mocks = vi.hoisted(() => ({
15
15
  deleteSession: vi.fn(),
16
16
  toggleWorkspacePanel: vi.fn(),
17
17
  selectSession: vi.fn(),
18
+ setListMode: vi.fn(),
19
+ toggleProjectCollapsed: vi.fn(),
18
20
  sessionItems: [] as NcpSessionListItemView[],
19
21
  isSessionListLoading: false,
20
22
  }));
@@ -27,6 +29,8 @@ vi.mock("@/features/chat/components/providers/chat-presenter.provider", () => ({
27
29
  },
28
30
  chatSessionListManager: {
29
31
  selectSession: mocks.selectSession,
32
+ setListMode: mocks.setListMode,
33
+ toggleProjectCollapsed: mocks.toggleProjectCollapsed,
30
34
  },
31
35
  }),
32
36
  }));
@@ -120,6 +124,8 @@ describe("ChatConversationHeaderSection", () => {
120
124
  mocks.deleteSession.mockReset();
121
125
  mocks.toggleWorkspacePanel.mockReset();
122
126
  mocks.selectSession.mockReset();
127
+ mocks.setListMode.mockReset();
128
+ mocks.toggleProjectCollapsed.mockReset();
123
129
  mocks.isSessionListLoading = false;
124
130
  viewportLayoutManager.resetForTests();
125
131
  useChatSessionListStore.persist.setOptions({
@@ -131,10 +137,25 @@ describe("ChatConversationHeaderSection", () => {
131
137
  useChatSessionListStore.setState({
132
138
  snapshot: {
133
139
  ...useChatSessionListStore.getState().snapshot,
140
+ collapsedProjectRoots: [],
141
+ listMode: "time-first",
142
+ pinnedProjectRoots: [],
143
+ pinnedSessionKeys: [],
134
144
  selectedAgentId: "main",
135
145
  selectedSessionKey: "parent-session-1",
136
146
  },
137
147
  });
148
+ mocks.setListMode.mockImplementation((listMode) => {
149
+ useChatSessionListStore.getState().setSnapshot({ listMode });
150
+ });
151
+ mocks.toggleProjectCollapsed.mockImplementation((projectRoot) => {
152
+ const { collapsedProjectRoots } = useChatSessionListStore.getState().snapshot;
153
+ useChatSessionListStore.getState().setSnapshot({
154
+ collapsedProjectRoots: collapsedProjectRoots.includes(projectRoot)
155
+ ? collapsedProjectRoots.filter((root) => root !== projectRoot)
156
+ : [...collapsedProjectRoots, projectRoot],
157
+ });
158
+ });
138
159
  useChatThreadStore.setState({
139
160
  snapshot: {
140
161
  ...useChatThreadStore.getState().snapshot,
@@ -229,6 +250,68 @@ describe("ChatConversationHeaderSection", () => {
229
250
  expect(mocks.selectSession).toHaveBeenCalledWith("session:ncp-2");
230
251
  });
231
252
 
253
+ it("uses lightweight static groups for pinned and activity dates", async () => {
254
+ const user = userEvent.setup();
255
+ viewportLayoutManager.setSidebarCollapsed(true);
256
+ useChatSessionListStore.setState({
257
+ snapshot: {
258
+ ...useChatSessionListStore.getState().snapshot,
259
+ pinnedSessionKeys: ["session:ncp-2"],
260
+ },
261
+ });
262
+
263
+ renderHeaderSection();
264
+ await user.click(screen.getByRole("button", { name: /Switch session/ }));
265
+
266
+ const pinnedGroup = screen.getByRole("region", { name: "Pinned" });
267
+ const pinnedTitle = within(pinnedGroup).getByText("Pinned");
268
+ expect(pinnedTitle.className).toContain("uppercase");
269
+ expect(pinnedTitle.className).not.toContain("bg-muted");
270
+ expect(within(pinnedGroup).queryByRole("button", { name: "Pinned" })).toBeNull();
271
+ expect(within(pinnedGroup).getByText("Background Task")).toBeTruthy();
272
+ expect(
273
+ within(screen.getByRole("region", { name: "Older" })).getByText("Parent Task"),
274
+ ).toBeTruthy();
275
+ });
276
+
277
+ it("switches to collapsible project groups inside the header popover", async () => {
278
+ const user = userEvent.setup();
279
+ viewportLayoutManager.setSidebarCollapsed(true);
280
+ mocks.sessionItems = [
281
+ createSessionListItem({
282
+ key: "parent-session-1",
283
+ label: "Parent Task",
284
+ projectName: "Alpha",
285
+ projectRoot: "/workspace/alpha",
286
+ }),
287
+ createSessionListItem({
288
+ key: "session:ncp-2",
289
+ label: "Background Task",
290
+ projectName: "Beta",
291
+ projectRoot: "/workspace/beta",
292
+ }),
293
+ ];
294
+
295
+ renderHeaderSection();
296
+ await user.click(screen.getByRole("button", { name: /Switch session/ }));
297
+ await user.click(screen.getByRole("button", { name: "Project" }));
298
+
299
+ expect(mocks.setListMode).toHaveBeenCalledWith("project-first");
300
+ const alphaGroup = screen.getByRole("region", { name: "Alpha" });
301
+ const alphaToggle = within(alphaGroup).getByRole("button", {
302
+ name: "Collapse project · Alpha",
303
+ });
304
+ expect(alphaToggle.getAttribute("aria-expanded")).toBe("true");
305
+ expect(alphaToggle.className).not.toContain("bg-muted");
306
+ expect(within(alphaGroup).getByText("Parent Task")).toBeTruthy();
307
+
308
+ await user.click(alphaToggle);
309
+ expect(mocks.toggleProjectCollapsed).toHaveBeenCalledWith("/workspace/alpha");
310
+ expect(
311
+ within(screen.getByRole("region", { name: "Alpha" })).queryByText("Parent Task"),
312
+ ).toBeNull();
313
+ });
314
+
232
315
  it("keeps the desktop title plain while the sidebar is expanded", () => {
233
316
  renderHeaderSection();
234
317
 
@@ -17,7 +17,10 @@ import { useChatSessionListStore } from "@/features/chat/stores/chat-session-lis
17
17
  import { useChatThreadStore } from "@/features/chat/stores/chat-thread.store";
18
18
  import { useChatQueryStore } from "@/features/chat/stores/ncp-chat-query.store";
19
19
  import { AgentIdentityAvatar } from "@/shared/components/common/agent-identity";
20
- import { getSessionProjectName } from "@/shared/lib/session-project";
20
+ import {
21
+ getSessionProjectName,
22
+ normalizeSessionProjectRootValue,
23
+ } from "@/shared/lib/session-project";
21
24
  import { t } from "@/shared/lib/i18n";
22
25
 
23
26
  type ChatConversationHeaderSectionProps = {
@@ -40,6 +43,9 @@ export function ChatConversationHeaderSection({
40
43
  const sessionTypesData = useChatQueryStore(
41
44
  (state) => state.snapshot.sessionTypesQuery?.data ?? null,
42
45
  );
46
+ const config = useChatQueryStore(
47
+ (state) => state.snapshot.configQuery?.data ?? null,
48
+ );
43
49
  const selectedSession = useNcpChatSelectedSession(sessionKey);
44
50
  const sessionTypeOptions = useMemo(
45
51
  () => buildSessionTypeOptions(sessionTypesData?.options ?? []),
@@ -55,6 +61,9 @@ export function ChatConversationHeaderSection({
55
61
  const shouldShowSessionTypeBadge =
56
62
  sessionType !== DEFAULT_SESSION_TYPE && Boolean(sessionTypeLabel);
57
63
  const sessionProjectRoot = selectedSession?.projectRoot ?? null;
64
+ const defaultWorkspacePath = normalizeSessionProjectRootValue(
65
+ config?.agents.defaults.workspace,
66
+ );
58
67
  const sessionProjectName =
59
68
  selectedSession?.projectName ?? getSessionProjectName(sessionProjectRoot);
60
69
  const canDeleteSession = Boolean(selectedSession);
@@ -129,6 +138,7 @@ export function ChatConversationHeaderSection({
129
138
  sessionKey={sessionKey ?? "draft"}
130
139
  projectName={sessionProjectName}
131
140
  projectRoot={sessionProjectRoot}
141
+ defaultWorkspacePath={defaultWorkspacePath}
132
142
  persistToServer={canDeleteSession}
133
143
  />
134
144
  ) : null
@@ -139,7 +149,8 @@ export function ChatConversationHeaderSection({
139
149
  sessionKey={sessionKey}
140
150
  canDeleteSession={canDeleteSession}
141
151
  isDeletePending={snapshot.isDeletePending}
142
- projectRoot={sessionProjectRoot}
152
+ currentPath={sessionProjectRoot ?? selectedSession?.workingDir ?? null}
153
+ defaultWorkspacePath={defaultWorkspacePath}
143
154
  metadata={selectedSession?.metadata ?? null}
144
155
  isWorkspaceOpen={isWorkspaceOpen}
145
156
  onToggleWorkspace={toggleWorkspace}
@@ -34,6 +34,7 @@ export function ChatConversationWorkspaceSection({
34
34
  activeSideChatDraft={activeSideChatDraft}
35
35
  workspaceFileTabs={workspaceFileTabs}
36
36
  activeWorkspaceFileKey={snapshot.activeWorkspaceFileKey ?? null}
37
+ closedWorkspaceTabEntries={snapshot.closedWorkspaceTabEntries}
37
38
  workspaceNavigationHistory={snapshot.workspaceNavigationHistory}
38
39
  workspaceNavigationHistoryIndex={snapshot.workspaceNavigationHistoryIndex}
39
40
  activePanelKind={snapshot.activeWorkspacePanelKind ?? null}
@@ -0,0 +1,22 @@
1
+ import type { NcpSessionListItemView } from '@/features/chat/features/ncp/hooks/use-ncp-session-list-view';
2
+ import { useChatQueryStore } from '@/features/chat/stores/ncp-chat-query.store';
3
+ import type { ChatSessionTypeOptionView } from '@/shared/lib/api';
4
+
5
+ export function createSidebarSessionItem(
6
+ session: NcpSessionListItemView['session'],
7
+ runStatus?: NcpSessionListItemView['runStatus'],
8
+ ): NcpSessionListItemView {
9
+ return { session, runStatus };
10
+ }
11
+
12
+ export function setSidebarSessionTypes(
13
+ options: ChatSessionTypeOptionView[],
14
+ defaultType = 'native',
15
+ ) {
16
+ useChatQueryStore.setState({
17
+ snapshot: {
18
+ ...useChatQueryStore.getState().snapshot,
19
+ sessionTypesQuery: { data: { defaultType, options } } as never,
20
+ },
21
+ });
22
+ }
@@ -3,11 +3,13 @@ import { fireEvent, render, screen, waitFor } from "@testing-library/react";
3
3
  import { MemoryRouter } from "react-router-dom";
4
4
  import { beforeEach, describe, expect, it, vi } from "vitest";
5
5
  import { ChatSidebar } from "@/features/chat/components/layout/chat-sidebar";
6
+ import {
7
+ createSidebarSessionItem as createSessionItem,
8
+ setSidebarSessionTypes as setSessionTypes,
9
+ } from "@/features/chat/components/layout/__tests__/chat-sidebar-test.utils";
6
10
  import type { NcpSessionListItemView } from "@/features/chat/features/ncp/hooks/use-ncp-session-list-view";
7
- import { useChatQueryStore } from "@/features/chat/stores/ncp-chat-query.store";
8
11
  import { useChatSessionListStore } from "@/features/chat/stores/chat-session-list.store";
9
12
  import { PREFERENCE_KEYS } from "@/shared/lib/api";
10
- import type { ChatSessionTypeOptionView } from "@/shared/lib/api";
11
13
  import { viewportLayoutManager } from "@/app/managers/viewport-layout.manager";
12
14
  const mocks = vi.hoisted(() => ({
13
15
  createSession: vi.fn(() => "draft-session-key"),
@@ -39,28 +41,15 @@ vi.mock("@/shared/lib/api", async (importOriginal) => {
39
41
  updatePreference: mocks.updatePreference,
40
42
  };
41
43
  });
42
- function createSessionItem(
43
- session: NcpSessionListItemView["session"],
44
- runStatus?: NcpSessionListItemView["runStatus"],
45
- ): NcpSessionListItemView {
46
- return { session, runStatus };
47
- }
48
- function setSessionTypes(
49
- options: ChatSessionTypeOptionView[],
50
- defaultType = "native",
51
- ) {
52
- useChatQueryStore.setState({
53
- snapshot: {
54
- ...useChatQueryStore.getState().snapshot,
55
- sessionTypesQuery: {
56
- data: {
57
- defaultType,
58
- options,
59
- },
60
- } as never,
61
- },
62
- });
63
- }
44
+ vi.mock("@/shared/hooks/use-projects", () => ({
45
+ useProjects: () => ({ data: { projects: [], templates: [] } }),
46
+ useCreateProject: () => ({
47
+ mutateAsync: vi.fn(),
48
+ reset: vi.fn(),
49
+ isPending: false,
50
+ error: null,
51
+ }),
52
+ }));
64
53
  function sidebarElement(variant?: "desktop" | "mobile") {
65
54
  const queryClient = new QueryClient({
66
55
  defaultOptions: {
@@ -18,6 +18,7 @@ import {
18
18
  BrainCircuit,
19
19
  PanelLeftClose,
20
20
  PanelLeftOpen,
21
+ Plus,
21
22
  } from "lucide-react";
22
23
  import { ChatSidebarSessionList } from "@/features/chat/features/session/components/chat-sidebar-session-list";
23
24
  import { ChatSidebarUtilityMenu } from "@/features/chat/components/layout/chat-sidebar-utility-menu";
@@ -134,6 +135,7 @@ export function ChatSidebarSessionArea({
134
135
  isCollapsed,
135
136
  isLoading,
136
137
  isProjectFirstView,
138
+ onCreateProject,
137
139
  onSelectMode,
138
140
  projectGroups,
139
141
  renderSessionItem,
@@ -144,6 +146,7 @@ export function ChatSidebarSessionArea({
144
146
  isCollapsed: boolean;
145
147
  isLoading: boolean;
146
148
  isProjectFirstView: boolean;
149
+ onCreateProject: () => void;
147
150
  onSelectMode: (mode: "time-first" | "project-first") => void;
148
151
  projectGroups: ReturnType<typeof groupSessionsByProject>;
149
152
  renderSessionItem: (item: NcpSessionListItemView) => JSX.Element;
@@ -161,10 +164,19 @@ export function ChatSidebarSessionArea({
161
164
  <div className="text-[11px] font-medium uppercase tracking-wider text-muted-foreground/75">
162
165
  {t("chatSidebarTaskRecords")}
163
166
  </div>
164
- <ChatSidebarListModeSwitch
165
- isProjectFirstView={isProjectFirstView}
166
- onSelectMode={onSelectMode}
167
- />
167
+ <div className="flex items-center gap-1">
168
+ {isProjectFirstView ? (
169
+ <IconActionButton
170
+ icon={<Plus className="h-3.5 w-3.5" />}
171
+ label={t("chatProjectCreate")}
172
+ onClick={onCreateProject}
173
+ />
174
+ ) : null}
175
+ <ChatSidebarListModeSwitch
176
+ isProjectFirstView={isProjectFirstView}
177
+ onSelectMode={onSelectMode}
178
+ />
179
+ </div>
168
180
  </div>
169
181
 
170
182
  <div
@@ -16,6 +16,9 @@ import { useChatSessionListStore } from "@/features/chat/stores/chat-session-lis
16
16
  import { useChatQueryStore } from "@/features/chat/stores/ncp-chat-query.store";
17
17
  import { useSystemStatus } from "@/features/system-status";
18
18
  import { useAgents } from "@/shared/hooks/use-agents";
19
+ import { useCreateProject, useProjects } from "@/shared/hooks/use-projects";
20
+ import type { ProjectCreateRequest } from "@/shared/lib/api";
21
+ import { normalizeSessionProjectRootValue } from "@/shared/lib/session-project";
19
22
  import { cn } from "@/shared/lib/utils";
20
23
  import { LANGUAGE_OPTIONS, t, type I18nLanguage } from "@/shared/lib/i18n";
21
24
  import { THEME_OPTIONS } from "@/shared/lib/theme";
@@ -42,6 +45,7 @@ import {
42
45
  import { useChatNewSessionTypePreference } from "@/features/chat/features/session-type/hooks/use-chat-new-session-type-preference";
43
46
  import { useViewportLayoutStore } from "@/app/stores/viewport-layout.store";
44
47
  import { SIDEBAR_RAIL_WIDTH_CLASS } from "@/app/components/layout/sidebar-rail.styles";
48
+ import { ChatProjectCreateDialog } from "@/features/chat/features/project/components/chat-project-create-dialog";
45
49
 
46
50
  type ChatSidebarVariant = "desktop" | "mobile";
47
51
 
@@ -94,13 +98,19 @@ export function ChatSidebar({
94
98
  (state) => state.isSidebarCollapsed,
95
99
  );
96
100
  const [isCreateMenuOpen, setIsCreateMenuOpen] = useState(false);
101
+ const [isProjectCreateOpen, setIsProjectCreateOpen] = useState(false);
97
102
  const [isUtilityMenuOpen, setIsUtilityMenuOpen] = useState(false);
98
103
  const listSnapshot = useChatSessionListStore((state) => state.snapshot);
99
104
  const sessionTypesData = useChatQueryStore(
100
105
  (state) => state.snapshot.sessionTypesQuery?.data ?? null,
101
106
  );
107
+ const config = useChatQueryStore(
108
+ (state) => state.snapshot.configQuery?.data ?? null,
109
+ );
102
110
  const systemStatus = useSystemStatus();
103
111
  const agentsQuery = useAgents();
112
+ const projectsQuery = useProjects();
113
+ const projectCreateMutation = useCreateProject();
104
114
  const { isLoading, items } = useNcpSessionListView();
105
115
  const { language, setLanguage } = useI18n();
106
116
  const { theme, setTheme } = useTheme();
@@ -153,8 +163,13 @@ export function ChatSidebar({
153
163
  [pinnedSessionKeys, sortedItems],
154
164
  );
155
165
  const projectGroups = useMemo(
156
- () => groupSessionsByProject(sortedItems, pinnedSessionKeys, pinnedProjectRoots),
157
- [pinnedProjectRoots, pinnedSessionKeys, sortedItems],
166
+ () => groupSessionsByProject(
167
+ sortedItems,
168
+ pinnedSessionKeys,
169
+ pinnedProjectRoots,
170
+ projectsQuery.data?.projects ?? [],
171
+ ),
172
+ [pinnedProjectRoots, pinnedSessionKeys, projectsQuery.data?.projects, sortedItems],
158
173
  );
159
174
  const sessionTypeOptions = useMemo(
160
175
  () => buildSessionTypeOptions(sessionTypesData?.options ?? []),
@@ -238,6 +253,14 @@ export function ChatSidebar({
238
253
  typeof projectRoot === "string" ? projectRoot : undefined,
239
254
  );
240
255
  };
256
+ const openProjectCreate = () => {
257
+ projectCreateMutation.reset();
258
+ setIsProjectCreateOpen(true);
259
+ };
260
+ const createProjectFromSidebar = async (input: ProjectCreateRequest): Promise<void> => {
261
+ await projectCreateMutation.mutateAsync(input);
262
+ setIsProjectCreateOpen(false);
263
+ };
241
264
 
242
265
  return (
243
266
  <aside
@@ -310,6 +333,7 @@ export function ChatSidebar({
310
333
  isCollapsed={shouldCollapse}
311
334
  isLoading={isLoading}
312
335
  isProjectFirstView={isProjectFirstView}
336
+ onCreateProject={openProjectCreate}
313
337
  onSelectMode={presenter.chatSessionListManager.setListMode}
314
338
  projectGroups={projectGroups}
315
339
  renderSessionItem={renderSessionItem}
@@ -338,6 +362,18 @@ export function ChatSidebar({
338
362
  themeOptions={utilityThemeOptions}
339
363
  />
340
364
  ) : null}
365
+
366
+ <ChatProjectCreateDialog
367
+ open={isProjectCreateOpen}
368
+ defaultWorkspacePath={normalizeSessionProjectRootValue(
369
+ config?.agents.defaults.workspace,
370
+ )}
371
+ templates={projectsQuery.data?.templates ?? []}
372
+ isCreating={projectCreateMutation.isPending}
373
+ errorMessage={projectCreateMutation.error?.message}
374
+ onOpenChange={setIsProjectCreateOpen}
375
+ onCreate={createProjectFromSidebar}
376
+ />
341
377
  </aside>
342
378
  );
343
379
  }
@@ -21,6 +21,7 @@ const mocks = vi.hoisted(() => {
21
21
  removeAttachment: vi.fn(),
22
22
  setSelectedModel: vi.fn(),
23
23
  setSelectedThinkingLevel: vi.fn(),
24
+ syncSessionPreferences: vi.fn(),
24
25
  setPendingSessionType: vi.fn(),
25
26
  setPendingProjectRoot: vi.fn(),
26
27
  setSelectedSkills: vi.fn(),
@@ -110,6 +110,42 @@ function createStreamingInputSnapshot(
110
110
  };
111
111
  }
112
112
 
113
+ function setImeDomText(textbox: HTMLElement, text: string): HTMLParagraphElement {
114
+ const paragraph = textbox.querySelector('p');
115
+ if (!paragraph) {
116
+ throw new Error('Expected the Lexical composer paragraph to exist.');
117
+ }
118
+ const range = document.createRange();
119
+ const lexicalText = paragraph.querySelector('[data-lexical-text="true"]')?.firstChild;
120
+ if (lexicalText?.nodeType === Node.TEXT_NODE) {
121
+ lexicalText.nodeValue = text;
122
+ range.setStart(lexicalText, text.length);
123
+ range.collapse(true);
124
+ } else {
125
+ paragraph.textContent = text;
126
+ range.selectNodeContents(paragraph);
127
+ range.collapse(false);
128
+ }
129
+ const selection = window.getSelection();
130
+ selection?.removeAllRanges();
131
+ selection?.addRange(range);
132
+ return paragraph;
133
+ }
134
+
135
+ async function insertText(textbox: HTMLElement, text: string): Promise<void> {
136
+ await act(async () => {
137
+ for (const character of text) {
138
+ textbox.dispatchEvent(new InputEvent('beforeinput', {
139
+ bubbles: true,
140
+ cancelable: true,
141
+ data: character,
142
+ inputType: 'insertText',
143
+ }));
144
+ await Promise.resolve();
145
+ }
146
+ });
147
+ }
148
+
113
149
  function StreamingSessionConversationInputHarness({
114
150
  controllerOverride = controller,
115
151
  controlRef,
@@ -145,6 +181,7 @@ function StreamingSessionConversationInputHarness({
145
181
  setSelectedModel: vi.fn(),
146
182
  setSelectedSkills: vi.fn(),
147
183
  setSelectedThinkingLevel: vi.fn(),
184
+ syncSessionPreferences: vi.fn(),
148
185
  setSendError: vi.fn(),
149
186
  syncComposer: vi.fn(),
150
187
  update: (patch: SessionConversationInputPatch) => {
@@ -209,23 +246,36 @@ describe('SessionConversationInput streaming stability', () => {
209
246
  expect(track?.firstElementChild?.className).toContain('px-0');
210
247
  });
211
248
 
212
- it('keeps IME composition stable while simulated streamed output rerenders the owner', async () => {
249
+ it('keeps a numbered IME candidate commit stable while streamed output rerenders the owner', async () => {
213
250
  const controlRef: MutableRefObject<StreamingInputControl | null> = { current: null };
214
251
  render(<StreamingSessionConversationInputHarness controlRef={controlRef} />);
215
252
 
216
253
  const textbox = screen.getByRole('textbox');
217
254
  fireEvent.focus(textbox);
255
+ await insertText(textbox, 'n');
218
256
  fireEvent.compositionStart(textbox);
257
+ const paragraph = setImeDomText(textbox, 'ni');
219
258
 
220
259
  act(() => {
221
260
  controlRef.current?.bumpStream();
222
261
  controlRef.current?.bumpStream();
223
262
  });
224
263
  expect(screen.getByTestId('stream-chunk').textContent).toBe('2');
225
-
264
+ expect(textbox.querySelector('p')).toBe(paragraph);
265
+ expect(textbox.textContent).toBe('ni');
266
+
267
+ fireEvent.keyDown(textbox, { key: '1', isComposing: true });
268
+ setImeDomText(textbox, '你');
269
+ fireEvent.input(textbox, {
270
+ data: '你',
271
+ inputType: 'insertCompositionText',
272
+ isComposing: true,
273
+ });
226
274
  fireEvent.compositionEnd(textbox, { data: '你' });
227
275
 
228
276
  await waitFor(() => expect(textbox.textContent).toBe('你'));
277
+ expect(textbox.querySelector('p')).toBe(paragraph);
278
+ await waitFor(() => expect(window.getSelection()?.anchorOffset).toBe(1));
229
279
  });
230
280
 
231
281
  it('renders queued inputs above the composer with edit and delete actions', () => {
@@ -188,6 +188,28 @@ export function SessionConversationArea(props: SessionConversationAreaProps) {
188
188
  inputSnapshot,
189
189
  setPendingSessionType: inputActions.setPendingSessionType,
190
190
  });
191
+ useEffect(() => {
192
+ inputActions.syncSessionPreferences({
193
+ defaultModel: inputQuery.defaultModel,
194
+ fallbackPreferredModel: inputQuery.fallbackPreferredModel,
195
+ fallbackPreferredThinking: inputQuery.fallbackPreferredThinking,
196
+ modelOptions: inputQuery.modelOptions,
197
+ selectedSessionExists: Boolean(inputQuery.selectedSession),
198
+ selectedSessionKey: inputQuery.selectedSessionKey,
199
+ selectedSessionPreferredModel:
200
+ inputQuery.selectedSession?.preferredModel ?? undefined,
201
+ selectedSessionPreferredThinking:
202
+ inputQuery.selectedSession?.preferredThinking ?? null,
203
+ });
204
+ }, [
205
+ inputActions,
206
+ inputQuery.defaultModel,
207
+ inputQuery.fallbackPreferredModel,
208
+ inputQuery.fallbackPreferredThinking,
209
+ inputQuery.modelOptions,
210
+ inputQuery.selectedSession,
211
+ inputQuery.selectedSessionKey,
212
+ ]);
191
213
  useSessionConversationDraftRouteState({
192
214
  sessionKey,
193
215
  setPendingProjectRoot: inputActions.setPendingProjectRoot,
@@ -67,6 +67,7 @@ import {
67
67
  import { SessionQueuedInputRows } from './session-queued-input-rows';
68
68
 
69
69
  type SessionConversationInputQuery = ReturnType<typeof useSessionConversationInputQuery>;
70
+ type SkillSource = SessionSkillEntryView['source'];
70
71
 
71
72
  export type SessionConversationInputController = {
72
73
  readonly canStopGeneration: boolean;
@@ -82,15 +83,18 @@ export type SessionConversationInputController = {
82
83
 
83
84
  function toSkillRecords(
84
85
  snapshotRecords: SessionSkillEntryView[],
85
- scopeLabels: Record<SessionSkillEntryView['scope'], string>,
86
+ scopeLabels: Record<SkillSource, string>,
87
+ groupLabels: Record<SkillSource, string>,
86
88
  ): ChatSkillRecord[] {
87
89
  return snapshotRecords.map((record) => ({
88
90
  key: record.ref,
89
91
  label: record.name,
90
- scopeLabel: scopeLabels[record.scope],
92
+ scopeLabel: scopeLabels[record.source],
93
+ groupKey: record.source,
94
+ groupLabel: groupLabels[record.source],
91
95
  description: record.description,
92
96
  descriptionZh: record.descriptionZh,
93
- badgeLabel: scopeLabels[record.scope],
97
+ badgeLabel: scopeLabels[record.source],
94
98
  }));
95
99
  }
96
100
 
@@ -109,12 +113,21 @@ function toModelRecords(snapshotModels: SessionConversationInputQuery['modelOpti
109
113
  }
110
114
 
111
115
  function useSessionConversationInputLabels(language: string) {
112
- const skillScopeLabels = useMemo<Record<'builtin' | 'project' | 'workspace', string>>(() => {
116
+ const { skillGroupLabels, skillScopeLabels } = useMemo(() => {
113
117
  void language;
114
118
  return {
115
- builtin: t('chatSkillScopeBuiltin'),
116
- project: t('chatSkillScopeProject'),
117
- workspace: t('chatSkillScopeWorkspace'),
119
+ skillScopeLabels: {
120
+ builtin: t('chatSkillScopeBuiltin'),
121
+ global: t('chatSkillScopeGlobal'),
122
+ project: t('chatSkillScopeProject'),
123
+ workspace: t('chatSkillScopeWorkspace'),
124
+ } satisfies Record<SkillSource, string>,
125
+ skillGroupLabels: {
126
+ builtin: t('chatSkillGroupBuiltin'),
127
+ global: t('chatSkillGroupGlobal'),
128
+ project: t('chatSkillGroupProject'),
129
+ workspace: t('chatSkillGroupWorkspace'),
130
+ } satisfies Record<SkillSource, string>,
118
131
  };
119
132
  }, [language]);
120
133
  const slashTexts = useMemo(() => {
@@ -128,6 +141,7 @@ function useSessionConversationInputLabels(language: string) {
128
141
  }, [language]);
129
142
  return {
130
143
  skillScopeLabels,
144
+ skillGroupLabels,
131
145
  slashTexts,
132
146
  recentModelsLabel: t('chatPickerRecentModels'),
133
147
  allModelsLabel: t('chatPickerAllModels'),
@@ -144,13 +158,15 @@ function useSessionConversationInputLabels(language: string) {
144
158
  function useSessionConversationInputCollections(params: {
145
159
  modelOptions: SessionConversationInputQuery['modelOptions'];
146
160
  skillRecords: SessionSkillEntryView[];
147
- skillScopeLabels: Record<'builtin' | 'project' | 'workspace', string>;
161
+ skillScopeLabels: Record<SkillSource, string>;
162
+ skillGroupLabels: Record<SkillSource, string>;
148
163
  }) {
164
+ const { modelOptions, skillGroupLabels, skillRecords: sourceSkillRecords, skillScopeLabels } = params;
149
165
  const skillRecords = useMemo(
150
- () => toSkillRecords(params.skillRecords, params.skillScopeLabels),
151
- [params.skillRecords, params.skillScopeLabels],
166
+ () => toSkillRecords(sourceSkillRecords, skillScopeLabels, skillGroupLabels),
167
+ [skillGroupLabels, skillScopeLabels, sourceSkillRecords],
152
168
  );
153
- const modelRecords = useMemo(() => toModelRecords(params.modelOptions), [params.modelOptions]);
169
+ const modelRecords = useMemo(() => toModelRecords(modelOptions), [modelOptions]);
154
170
  return {
155
171
  skillRecords,
156
172
  modelRecords,
@@ -203,6 +219,7 @@ export const SessionConversationInput = memo(function SessionConversationInput(p
203
219
  } = useSessionConversationInputCollections({
204
220
  modelOptions: inputQuery.modelOptions,
205
221
  skillRecords: inputQuery.skillRecords,
222
+ skillGroupLabels: labels.skillGroupLabels,
206
223
  skillScopeLabels: labels.skillScopeLabels,
207
224
  });
208
225
  const slashCommands = useMemo(() => {
@@ -232,6 +249,9 @@ export const SessionConversationInput = memo(function SessionConversationInput(p
232
249
  }),
233
250
  [presenter.chatUiManager],
234
251
  );
252
+ const contextReferenceProjectRoot = inputQuery.selectedSessionKey
253
+ ? inputQuery.selectedSession?.projectRoot ?? inputQuery.defaultProjectRoot ?? ''
254
+ : inputSnapshot.pendingProjectRoot ?? inputQuery.defaultProjectRoot ?? '';
235
255
  const { inputSurfaceState, setInputSurfaceTrigger } = useChatInputSurfaceState({
236
256
  commands: slashCommands,
237
257
  isSkillsLoading: inputQuery.isSkillsLoading,
@@ -241,6 +261,7 @@ export const SessionConversationInput = memo(function SessionConversationInput(p
241
261
  language,
242
262
  onSelectPanelApp: handleSlashPanelAppSelect,
243
263
  onSelectSkill: chatRecentSkillsManager.remember,
264
+ projectRoot: contextReferenceProjectRoot,
244
265
  recentSkillValues,
245
266
  skillRecords,
246
267
  });
@@ -252,15 +273,9 @@ export const SessionConversationInput = memo(function SessionConversationInput(p
252
273
  favoriteModelValues,
253
274
  setModelFavorite,
254
275
  } = useChatModelFavorites(modelRecordValues);
255
- const selectedModel =
256
- inputSnapshot.selectedModel ??
257
- inputQuery.fallbackPreferredModel ??
258
- inputQuery.defaultModel ??
259
- '';
276
+ const selectedModel = inputSnapshot.selectedModel ?? '';
260
277
  const selectedThinkingLevel = (
261
- inputSnapshot.selectedThinkingLevel ??
262
- inputQuery.fallbackPreferredThinking ??
263
- null
278
+ inputSnapshot.selectedThinkingLevel
264
279
  ) as ChatThinkingLevel | null;
265
280
  const availabilitySnapshot = {
266
281
  isProviderStateResolved: inputQuery.isProviderStateResolved,
@@ -391,6 +406,7 @@ export const SessionConversationInput = memo(function SessionConversationInput(p
391
406
  skillRecords,
392
407
  selectedSkills: inputSnapshot.selectedSkills,
393
408
  });
409
+ const composerNodes = useMemo(() => [...inputSnapshot.nodes], [inputSnapshot.nodes]);
394
410
 
395
411
  const useReadingTrack = surface === 'default' && messageLayout === 'flat';
396
412
  const inputBar = (
@@ -403,7 +419,7 @@ export const SessionConversationInput = memo(function SessionConversationInput(p
403
419
  ? <SessionQueuedInputRows controller={controller} />
404
420
  : null}
405
421
  composer={{
406
- nodes: [...inputSnapshot.nodes],
422
+ nodes: composerNodes,
407
423
  placeholder: textareaPlaceholder,
408
424
  disabled: inputDisabled,
409
425
  onNodesChange: handleNodesChange,