@vibe-forge/client 2.0.0 → 2.0.1

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 (347) hide show
  1. package/AGENTS.md +4 -1
  2. package/dist/assets/{arc-CbOXL0l9.js → arc-CqviK3HX.js} +1 -1
  3. package/dist/assets/{blockDiagram-c4efeb88-CqxINvsS.js → blockDiagram-c4efeb88-BEp50UHp.js} +1 -1
  4. package/dist/assets/{c4Diagram-c83219d4-BKazU0hb.js → c4Diagram-c83219d4-C5w55JzM.js} +1 -1
  5. package/dist/assets/channel-C6LTxxLg.js +1 -0
  6. package/dist/assets/{classDiagram-beda092f-fAFX5BpB.js → classDiagram-beda092f-CQJVtHEy.js} +1 -1
  7. package/dist/assets/{classDiagram-v2-2358418a-w1VkNGJj.js → classDiagram-v2-2358418a-B37Xl9jB.js} +1 -1
  8. package/dist/assets/clone-CG6ZcokX.js +1 -0
  9. package/dist/assets/{createText-1719965b-CEinakVP.js → createText-1719965b-9YwvWMdV.js} +1 -1
  10. package/dist/assets/{cssMode-DPqRki4y.js → cssMode-BX88r5f4.js} +1 -1
  11. package/dist/assets/{edges-96097737-Cb0F1_3K.js → edges-96097737-CNHoXVrD.js} +1 -1
  12. package/dist/assets/{erDiagram-0228fc6a-C-N2fx-J.js → erDiagram-0228fc6a-BoYldy0g.js} +1 -1
  13. package/dist/assets/{flowDb-c6c81e3f-D1Xz_8Gf.js → flowDb-c6c81e3f-CoPw_R-Q.js} +1 -1
  14. package/dist/assets/{flowDiagram-50d868cf-DyPSZyAj.js → flowDiagram-50d868cf-nCqbSXd-.js} +1 -1
  15. package/dist/assets/flowDiagram-v2-4f6560a1-CSZTI7GQ.js +1 -0
  16. package/dist/assets/{flowchart-elk-definition-6af322e1-Dr1DDXwE.js → flowchart-elk-definition-6af322e1-BwMuPTrV.js} +1 -1
  17. package/dist/assets/{freemarker2-C3DvPFaK.js → freemarker2-DUFDSvgj.js} +1 -1
  18. package/dist/assets/{ganttDiagram-a2739b55-DmvY1GRj.js → ganttDiagram-a2739b55-CLNH3S_C.js} +1 -1
  19. package/dist/assets/{gitGraphDiagram-82fe8481-CoXfPYYi.js → gitGraphDiagram-82fe8481-uDu1ectX.js} +1 -1
  20. package/dist/assets/{graph-BkDQy7Qt.js → graph-DuC4kt4I.js} +1 -1
  21. package/dist/assets/{handlebars-BcTFdqjl.js → handlebars-BSd4a6l9.js} +1 -1
  22. package/dist/assets/{html-Dg-O6XFr.js → html-H48gEjQd.js} +1 -1
  23. package/dist/assets/{htmlMode-B_wqYWvn.js → htmlMode-Nqw7-Nqh.js} +1 -1
  24. package/dist/assets/{index-5325376f-kxPTR3_e.js → index-5325376f-rnz0GXAT.js} +1 -1
  25. package/dist/assets/{index-wkhI4dr6.js → index-DeQLT67a.js} +398 -377
  26. package/dist/assets/index-DiOCtPLP.css +32 -0
  27. package/dist/assets/{infoDiagram-8eee0895-BEvqkwPI.js → infoDiagram-8eee0895-BsGB550b.js} +1 -1
  28. package/dist/assets/{javascript-DhlOH8_z.js → javascript-0g2herYV.js} +1 -1
  29. package/dist/assets/{journeyDiagram-c64418c1-gKtLYmmp.js → journeyDiagram-c64418c1-DLldlz0H.js} +1 -1
  30. package/dist/assets/{jsonMode-DxTbF9OD.js → jsonMode-CN5ZURMh.js} +1 -1
  31. package/dist/assets/{layout-CDaZEk6E.js → layout-QKUiDNJK.js} +1 -1
  32. package/dist/assets/{line-DNRQu8iq.js → line-CeP3XWjD.js} +1 -1
  33. package/dist/assets/{linear-Cph9Z6_j.js → linear-74cQVgWT.js} +1 -1
  34. package/dist/assets/{liquid-ByZ6JgRG.js → liquid-B6cRrfrb.js} +1 -1
  35. package/dist/assets/{lspLanguageFeatures-DzvhkgnM.js → lspLanguageFeatures-C5ogOh5E.js} +1 -1
  36. package/dist/assets/{mdx-D8RGHTl6.js → mdx-BBIy-KRj.js} +1 -1
  37. package/dist/assets/{mermaid.core-BgcryF__.js → mermaid.core-BhdbV0mr.js} +4 -4
  38. package/dist/assets/{mindmap-definition-8da855dc-WrxK0FcB.js → mindmap-definition-8da855dc-B67VKJuD.js} +1 -1
  39. package/dist/assets/{pieDiagram-a8764435-VsZBsiQy.js → pieDiagram-a8764435-Cxv9WY_E.js} +1 -1
  40. package/dist/assets/{python-CXVtk_cg.js → python-CBdGo8__.js} +1 -1
  41. package/dist/assets/{quadrantDiagram-1e28029f-BVlgwOvU.js → quadrantDiagram-1e28029f-BTkj65P_.js} +1 -1
  42. package/dist/assets/{razor-0tind7h2.js → razor-azKH0Dwj.js} +1 -1
  43. package/dist/assets/{requirementDiagram-08caed73-CpPMPoYp.js → requirementDiagram-08caed73-D4jVXpOT.js} +1 -1
  44. package/dist/assets/{sankeyDiagram-a04cb91d-Cm5nnRmc.js → sankeyDiagram-a04cb91d-CXhutIA1.js} +1 -1
  45. package/dist/assets/{sequenceDiagram-c5b8d532-DpMlJvJB.js → sequenceDiagram-c5b8d532-B56TTZlx.js} +1 -1
  46. package/dist/assets/{stateDiagram-1ecb1508-DU1zc7vq.js → stateDiagram-1ecb1508-Cs0plMcS.js} +1 -1
  47. package/dist/assets/{stateDiagram-v2-c2b004d7-D-0RgmAp.js → stateDiagram-v2-c2b004d7-LSJaXPJN.js} +1 -1
  48. package/dist/assets/{styles-b4e223ce-BSO-yNWV.js → styles-b4e223ce-UdXfHMuu.js} +1 -1
  49. package/dist/assets/{styles-ca3715f6-CHnsn2Ro.js → styles-ca3715f6-EuRy_hTu.js} +1 -1
  50. package/dist/assets/{styles-d45a18b0-B-rVGjEq.js → styles-d45a18b0-B24zVoK3.js} +1 -1
  51. package/dist/assets/{svgDrawCommon-b86b1483-CA3Pl89f.js → svgDrawCommon-b86b1483-B2S0NW3K.js} +1 -1
  52. package/dist/assets/{timeline-definition-faaaa080-BcihLR6s.js → timeline-definition-faaaa080-DFWKh9mU.js} +1 -1
  53. package/dist/assets/{tsMode-D9GGa5Ur.js → tsMode-FZsHWiOn.js} +1 -1
  54. package/dist/assets/{typescript-BT9CK_EL.js → typescript-CYdJ3s3D.js} +1 -1
  55. package/dist/assets/{xml-DNO75J-T.js → xml-C16X_hpZ.js} +1 -1
  56. package/dist/assets/{xychartDiagram-f5964ef8-DJTwe32X.js → xychartDiagram-f5964ef8-DyBiBYci.js} +1 -1
  57. package/dist/assets/{yaml-7CVzhiP2.js → yaml-CRjA4-Rj.js} +1 -1
  58. package/dist/index.html +8 -3
  59. package/dist/manifest.webmanifest +30 -0
  60. package/dist/pwa-icon-192.png +0 -0
  61. package/dist/pwa-icon-512.png +0 -0
  62. package/dist/sw.js +105 -0
  63. package/index.html +6 -1
  64. package/package.json +13 -13
  65. package/public/manifest.webmanifest +30 -0
  66. package/public/pwa-icon-192.png +0 -0
  67. package/public/pwa-icon-512.png +0 -0
  68. package/public/sw.js +105 -0
  69. package/src/App.tsx +13 -1
  70. package/src/api/README.md +1 -0
  71. package/src/api/adapters.ts +63 -0
  72. package/src/api/auth-token.ts +51 -0
  73. package/src/api/auth.ts +46 -0
  74. package/src/api/automation.ts +10 -0
  75. package/src/api/base.ts +20 -17
  76. package/src/api/config.ts +5 -1
  77. package/src/api/knowledge.ts +59 -0
  78. package/src/api/sessions.ts +35 -3
  79. package/src/api/skill-hub.ts +126 -0
  80. package/src/api/workspace.ts +33 -1
  81. package/src/api/worktree-environments.ts +53 -0
  82. package/src/api.ts +62 -7
  83. package/src/client-build-info.ts +19 -0
  84. package/src/components/ConfigView.scss +595 -28
  85. package/src/components/ConfigView.tsx +568 -138
  86. package/src/components/NavRail.scss +1 -2
  87. package/src/components/NavRail.tsx +33 -3
  88. package/src/components/Sidebar.scss +0 -44
  89. package/src/components/Sidebar.tsx +109 -37
  90. package/src/components/auth/AuthGate.scss +79 -0
  91. package/src/components/auth/AuthGate.tsx +174 -0
  92. package/src/components/automation-view/@components/AutomationTaskComposer.tsx +218 -0
  93. package/src/components/automation-view/@components/AutomationTriggerRow.tsx +192 -0
  94. package/src/components/automation-view/@hooks/use-automation-startup-options-data.tsx +289 -0
  95. package/src/components/automation-view/@hooks/use-automation-startup-static-options.ts +51 -0
  96. package/src/components/automation-view/@utils/sender-model-options.tsx +52 -0
  97. package/src/components/automation-view/@utils/startup-options.ts +26 -0
  98. package/src/components/automation-view/AutomationEmptyGuide.tsx +61 -0
  99. package/src/components/automation-view/AutomationEmptyLanding.scss +165 -0
  100. package/src/components/automation-view/AutomationEmptyLanding.tsx +199 -0
  101. package/src/components/automation-view/AutomationRuleDetailPreview.tsx +179 -0
  102. package/src/components/automation-view/PanelTitleActions.tsx +66 -0
  103. package/src/components/automation-view/RuleFormPanel.scss +172 -49
  104. package/src/components/automation-view/RuleFormPanel.tsx +196 -91
  105. package/src/components/automation-view/RuleSidebar.scss +128 -41
  106. package/src/components/automation-view/RuleSidebar.tsx +173 -89
  107. package/src/components/automation-view/RunHistoryPanel.scss +307 -72
  108. package/src/components/automation-view/RunHistoryPanel.tsx +185 -165
  109. package/src/components/automation-view/TaskList.scss +126 -64
  110. package/src/components/automation-view/TaskList.tsx +15 -31
  111. package/src/components/automation-view/TriggerList.scss +87 -8
  112. package/src/components/automation-view/TriggerList.tsx +14 -173
  113. package/src/components/automation-view/index.scss +165 -37
  114. package/src/components/automation-view/index.tsx +174 -87
  115. package/src/components/automation-view/types.ts +13 -0
  116. package/src/components/chat/AGENTS.md +3 -1
  117. package/src/components/chat/ChatHeader.tsx +56 -33
  118. package/src/components/chat/ChatHistoryView.tsx +250 -121
  119. package/src/components/chat/NewSessionGuide.scss +274 -204
  120. package/src/components/chat/NewSessionGuide.tsx +40 -111
  121. package/src/components/chat/NewSessionGuideStarterList.tsx +187 -0
  122. package/src/components/chat/NewSessionGuideStarterSection.tsx +120 -0
  123. package/src/components/chat/bottom-dock-constants.ts +4 -0
  124. package/src/components/chat/conversation-starter-apply.ts +181 -0
  125. package/src/components/chat/git-controls/ChatGitControls.scss +65 -0
  126. package/src/components/chat/git-controls/DraftGitControls.tsx +14 -0
  127. package/src/components/chat/git-controls/DraftWorktreeEnvironmentDropdown.tsx +115 -0
  128. package/src/components/chat/messages/MessageItem.tsx +3 -2
  129. package/src/components/chat/messages/MessageStatusNotice.tsx +12 -4
  130. package/src/components/chat/messages/build-chat-history-status-notices.ts +18 -13
  131. package/src/components/chat/messages/message-action-utils.ts +18 -0
  132. package/src/components/chat/new-session-guide-config.ts +19 -0
  133. package/src/components/chat/new-session-guide-items.ts +172 -0
  134. package/src/components/chat/new-session-guide-list-order.ts +58 -0
  135. package/src/components/chat/sender/@components/account-select/AccountSelectControl.scss +112 -0
  136. package/src/components/chat/sender/@components/account-select/AccountSelectControl.tsx +280 -0
  137. package/src/components/chat/sender/@components/account-select/AccountSelectDropdown.scss +155 -0
  138. package/src/components/chat/sender/@components/adapter-select/AdapterSelectControl.scss +51 -12
  139. package/src/components/chat/sender/@components/adapter-select/AdapterSelectDropdown.scss +14 -0
  140. package/src/components/chat/sender/@components/effort-select/EffortSelectControl.scss +36 -0
  141. package/src/components/chat/sender/@components/effort-select/EffortSelectControl.tsx +17 -12
  142. package/src/components/chat/sender/@components/model-select/ModelSelectControl.scss +62 -0
  143. package/src/components/chat/sender/@components/model-select/ModelSelectControl.tsx +17 -12
  144. package/src/components/chat/sender/@components/model-select/ModelSelectMenu.scss +2 -0
  145. package/src/components/chat/sender/@components/model-select/ModelSelectMenuLabels.scss +24 -0
  146. package/src/components/chat/sender/@components/permission-mode-control/PermissionModeControl.scss +199 -0
  147. package/src/components/chat/sender/@components/permission-mode-control/PermissionModeControl.tsx +172 -0
  148. package/src/components/chat/sender/@components/reference-actions/ReferenceActionsControl.scss +1 -10
  149. package/src/components/chat/sender/@components/reference-actions/ReferenceActionsControl.tsx +16 -65
  150. package/src/components/chat/sender/@components/sender-body/SenderBody.tsx +13 -1
  151. package/src/components/chat/sender/@components/sender-header-controls/SenderHeaderControls.tsx +157 -0
  152. package/src/components/chat/sender/@components/sender-monaco-editor/monaco-runtime.ts +1 -17
  153. package/src/components/chat/sender/@components/sender-toolbar/SenderSelectBase.scss +18 -2
  154. package/src/components/chat/sender/@components/sender-toolbar/SenderSelectShared.scss +18 -1
  155. package/src/components/chat/sender/@components/sender-toolbar/SenderToolbar.scss +2 -0
  156. package/src/components/chat/sender/@components/sender-toolbar/SenderToolbar.tsx +40 -40
  157. package/src/components/chat/sender/@components/session-target/SenderSessionTargetBar.scss +215 -0
  158. package/src/components/chat/sender/@components/session-target/SenderSessionTargetBar.tsx +185 -0
  159. package/src/components/chat/sender/@core/build-sender-toolbar.ts +6 -0
  160. package/src/components/chat/sender/@core/create-sender-toolbar-handlers.ts +21 -1
  161. package/src/components/chat/sender/@core/get-sender-runtime-state.ts +3 -2
  162. package/src/components/chat/sender/@core/sender-toolbar-bindings.ts +12 -0
  163. package/src/components/chat/sender/@hooks/use-sender-controller.ts +56 -1
  164. package/src/components/chat/sender/@hooks/use-sender-reference-actions.ts +9 -66
  165. package/src/components/chat/sender/@types/sender-props.ts +18 -0
  166. package/src/components/chat/sender/@types/sender-toolbar-types.ts +8 -1
  167. package/src/components/chat/sender/@types/sender-types.ts +1 -3
  168. package/src/components/chat/sender/@utils/sender-constants.ts +1 -1
  169. package/src/components/chat/sender/Sender.scss +245 -2
  170. package/src/components/chat/sender/Sender.tsx +1 -0
  171. package/src/components/chat/status-bar/ChatStatusBar.scss +85 -0
  172. package/src/components/chat/status-bar/ChatStatusBar.tsx +48 -0
  173. package/src/components/chat/terminal/@components/TerminalManagerList.tsx +191 -0
  174. package/src/components/chat/terminal/@components/TerminalPane.scss +71 -0
  175. package/src/components/chat/terminal/@components/TerminalPane.tsx +137 -0
  176. package/src/components/chat/terminal/@components/TerminalPanelActions.tsx +75 -0
  177. package/src/components/chat/terminal/@hooks/use-terminal-instance.ts +36 -0
  178. package/src/components/chat/terminal/@hooks/use-terminal-session.ts +18 -21
  179. package/src/components/chat/terminal/@hooks/use-terminal-title-editor.ts +72 -0
  180. package/src/components/chat/terminal/@utils/terminal-keyboard.ts +141 -0
  181. package/src/components/chat/terminal/@utils/terminal-panes.ts +123 -0
  182. package/src/components/chat/terminal/ChatTerminalView.scss +310 -38
  183. package/src/components/chat/terminal/ChatTerminalView.tsx +151 -79
  184. package/src/components/chat/tools/core/ToolDiffViewer.tsx +3 -17
  185. package/src/components/chat/workspace-drawer/ChatWorkspaceDrawer.scss +778 -0
  186. package/src/components/chat/workspace-drawer/ChatWorkspaceDrawer.tsx +112 -0
  187. package/src/components/chat/workspace-drawer/ChatWorkspaceDrawerToolbar.tsx +183 -0
  188. package/src/components/chat/workspace-drawer/WorkspaceDrawerChangedFileRow.tsx +75 -0
  189. package/src/components/chat/workspace-drawer/WorkspaceDrawerChangedFiles.tsx +161 -0
  190. package/src/components/chat/workspace-drawer/WorkspaceDrawerChangedFolderTree.tsx +191 -0
  191. package/src/components/chat/workspace-drawer/WorkspaceDrawerTree.tsx +35 -0
  192. package/src/components/chat/workspace-drawer/WorkspaceDrawerTreeState.tsx +17 -0
  193. package/src/components/chat/workspace-drawer/changed-files-model.ts +152 -0
  194. package/src/components/chat/workspace-drawer/workspace-drawer-icons.ts +110 -0
  195. package/src/components/chat/workspace-file-editor/WorkspaceFileBreadcrumb.tsx +17 -0
  196. package/src/components/chat/workspace-file-editor/WorkspaceFileEditorView.scss +283 -0
  197. package/src/components/chat/workspace-file-editor/WorkspaceFileEditorView.tsx +165 -0
  198. package/src/components/chat/workspace-file-editor/WorkspaceFileTabs.tsx +135 -0
  199. package/src/components/chat/workspace-file-editor/use-workspace-file-editor-state.ts +113 -0
  200. package/src/components/chat/workspace-file-editor/workspace-file-editor-language.ts +55 -0
  201. package/src/components/composer-landing/ComposerLanding.scss +75 -0
  202. package/src/components/composer-landing/ComposerLanding.tsx +47 -0
  203. package/src/components/config/AGENTS.md +45 -0
  204. package/src/components/config/AdapterAccountsManager.scss +540 -0
  205. package/src/components/config/AdapterAccountsManager.tsx +846 -0
  206. package/src/components/config/AppSettingsPanel.tsx +24 -2
  207. package/src/components/config/ConfigAboutSection.scss +7 -1
  208. package/src/components/config/ConfigAboutSection.tsx +21 -3
  209. package/src/components/config/ConfigEditors.scss +12 -0
  210. package/src/components/config/ConfigFieldRow.scss +88 -4
  211. package/src/components/config/ConfigSectionForm.scss +88 -3
  212. package/src/components/config/ConfigSectionForm.tsx +948 -138
  213. package/src/components/config/ConfigSectionPanel.tsx +188 -12
  214. package/src/components/config/ConfigSourceSwitch.tsx +32 -18
  215. package/src/components/config/DetailCollectionFieldActions.tsx +63 -0
  216. package/src/components/config/DetailListField.tsx +413 -0
  217. package/src/components/config/McpServerItemEditor.tsx +154 -0
  218. package/src/components/config/RecommendedModelsItemEditor.tsx +146 -0
  219. package/src/components/config/WorktreeEnvironmentDetailView.tsx +126 -0
  220. package/src/components/config/WorktreeEnvironmentListView.tsx +126 -0
  221. package/src/components/config/WorktreeEnvironmentPanel.scss +430 -0
  222. package/src/components/config/WorktreeEnvironmentPanel.tsx +147 -0
  223. package/src/components/config/WorktreeEnvironmentScriptEditorCard.tsx +125 -0
  224. package/src/components/config/WorktreeEnvironmentScriptEditors.tsx +189 -0
  225. package/src/components/config/configConflict.ts +41 -0
  226. package/src/components/config/configDetail.ts +381 -0
  227. package/src/components/config/configSchema.ts +850 -179
  228. package/src/components/config/configUtils.ts +1 -1
  229. package/src/components/config/record-editors/RecordEditors.scss +187 -2
  230. package/src/components/config/record-editors/RecordJsonEditor.tsx +27 -2
  231. package/src/components/config/record-editors/SchemaObjectEditor.tsx +183 -0
  232. package/src/components/config/record-editors/SchemaRecordEditor.tsx +184 -0
  233. package/src/components/config/record-editors/index.tsx +2 -1
  234. package/src/components/config/record-editors/schemaRecordUtils.ts +55 -0
  235. package/src/components/config/use-worktree-environment-auto-save.ts +386 -0
  236. package/src/components/config/worktree-environment-panel-model.ts +108 -0
  237. package/src/components/dock-panel/DockPanel.scss +84 -17
  238. package/src/components/dock-panel/DockPanel.tsx +37 -34
  239. package/src/components/dock-panel/DockPanelHeader.tsx +65 -0
  240. package/src/components/dock-panel/use-dock-panel-fullscreen.ts +51 -0
  241. package/src/components/knowledge-base/KnowledgeBaseView.scss +276 -38
  242. package/src/components/knowledge-base/KnowledgeBaseView.tsx +252 -46
  243. package/src/components/knowledge-base/components/ActionButton.scss +30 -4
  244. package/src/components/knowledge-base/components/ActionButton.tsx +13 -3
  245. package/src/components/knowledge-base/components/CreateSkillModal.tsx +59 -0
  246. package/src/components/knowledge-base/components/EmptyState.scss +4 -2
  247. package/src/components/knowledge-base/components/EntitiesTab.tsx +20 -20
  248. package/src/components/knowledge-base/components/EntityList.scss +3 -0
  249. package/src/components/knowledge-base/components/FilterBar.scss +1 -0
  250. package/src/components/knowledge-base/components/FilterBar.tsx +12 -8
  251. package/src/components/knowledge-base/components/FlowsTab.tsx +20 -20
  252. package/src/components/knowledge-base/components/KnowledgeBaseHeader.tsx +7 -6
  253. package/src/components/knowledge-base/components/KnowledgeContentControls.tsx +35 -0
  254. package/src/components/knowledge-base/components/KnowledgeList.scss +14 -3
  255. package/src/components/knowledge-base/components/KnowledgeMobilePanel.tsx +122 -0
  256. package/src/components/knowledge-base/components/KnowledgeSidebar.tsx +97 -0
  257. package/src/components/knowledge-base/components/LoadingState.scss +2 -1
  258. package/src/components/knowledge-base/components/ProjectSkillsList.tsx +79 -0
  259. package/src/components/knowledge-base/components/RuleList.scss +3 -0
  260. package/src/components/knowledge-base/components/RulesTab.tsx +31 -30
  261. package/src/components/knowledge-base/components/SectionHeader.scss +13 -1
  262. package/src/components/knowledge-base/components/SectionHeader.tsx +5 -3
  263. package/src/components/knowledge-base/components/SkillArchiveInput.tsx +43 -0
  264. package/src/components/knowledge-base/components/SkillHubResultItem.tsx +112 -0
  265. package/src/components/knowledge-base/components/SkillMarketResults.tsx +98 -0
  266. package/src/components/knowledge-base/components/SkillMarketView.tsx +198 -0
  267. package/src/components/knowledge-base/components/SkillMarketView.types.ts +28 -0
  268. package/src/components/knowledge-base/components/SkillRegistryErrors.tsx +21 -0
  269. package/src/components/knowledge-base/components/SkillRegistryModal.tsx +74 -0
  270. package/src/components/knowledge-base/components/SkillsCliModal.tsx +154 -0
  271. package/src/components/knowledge-base/components/SkillsTab.scss +424 -0
  272. package/src/components/knowledge-base/components/SkillsTab.tsx +319 -35
  273. package/src/components/knowledge-base/components/SkillsTabActions.tsx +88 -0
  274. package/src/components/knowledge-base/components/SpecList.scss +3 -0
  275. package/src/components/knowledge-base/components/TabContent.scss +4 -3
  276. package/src/components/knowledge-base/components/skill-hub-utils.ts +108 -0
  277. package/src/components/knowledge-base/components/use-skill-market-filters.ts +37 -0
  278. package/src/components/knowledge-base/components/use-skill-market-query-input.ts +44 -0
  279. package/src/components/knowledge-base/components/use-skill-market-search.ts +49 -0
  280. package/src/components/knowledge-base/components/use-skill-registry-modal.ts +68 -0
  281. package/src/components/monaco/monaco-runtime.ts +44 -0
  282. package/src/components/monaco/use-monaco-theme.ts +63 -0
  283. package/src/components/nav-rail-account-actions.tsx +104 -0
  284. package/src/components/server-connection/ServerConnectionGate.scss +356 -0
  285. package/src/components/server-connection/ServerConnectionGate.tsx +238 -0
  286. package/src/components/server-connection/ServerConnectionProfileModal.tsx +145 -0
  287. package/src/components/server-connection/ServerConnectionProfiles.tsx +113 -0
  288. package/src/components/server-connection/ServerConnectionUrlInput.tsx +85 -0
  289. package/src/components/sidebar/SidebarHeader.scss +5 -41
  290. package/src/components/sidebar/SidebarHeader.tsx +74 -66
  291. package/src/components/sidebar/SidebarHeaderSearchActions.tsx +24 -28
  292. package/src/components/sidebar-list/SidebarListHeader.scss +246 -0
  293. package/src/components/sidebar-list/SidebarListHeader.tsx +146 -0
  294. package/src/components/workspace/ContextFilePicker.scss +9 -26
  295. package/src/components/workspace/ContextFilePicker.tsx +31 -113
  296. package/src/components/workspace/context-file-types.ts +36 -0
  297. package/src/components/workspace/project-file-tree/ProjectFileTree.scss +298 -0
  298. package/src/components/workspace/project-file-tree/ProjectFileTree.tsx +138 -0
  299. package/src/components/workspace/project-file-tree/ProjectFileTreeRow.tsx +167 -0
  300. package/src/components/workspace/project-file-tree/ProjectFileTreeRowContextMenu.tsx +106 -0
  301. package/src/components/workspace/project-file-tree/ProjectFileTreeRows.tsx +139 -0
  302. package/src/components/workspace/project-file-tree/project-file-tree-helpers.ts +101 -0
  303. package/src/components/workspace/project-file-tree/project-file-tree-icons.ts +93 -0
  304. package/src/components/workspace/project-file-tree/project-file-tree-types.ts +27 -0
  305. package/src/components/workspace/project-file-tree/use-project-file-tree-data.ts +197 -0
  306. package/src/components/workspace/project-file-tree/use-project-file-tree-selection.ts +144 -0
  307. package/src/hooks/chat/chat-session-target.ts +69 -0
  308. package/src/hooks/chat/chat-session-workspace-draft.ts +11 -4
  309. package/src/hooks/chat/interaction-state.ts +1 -0
  310. package/src/hooks/chat/optimistic-session-creation.ts +189 -0
  311. package/src/hooks/chat/use-chat-adapter-account-selection.tsx +156 -0
  312. package/src/hooks/chat/use-chat-route-bottom-panel.ts +181 -0
  313. package/src/hooks/chat/use-chat-route-deep-link-view.ts +33 -0
  314. package/src/hooks/chat/use-chat-session-actions.ts +259 -65
  315. package/src/hooks/chat/use-chat-session-messages.ts +71 -4
  316. package/src/hooks/chat/use-chat-session.ts +36 -1
  317. package/src/hooks/chat/workspace-file-panel-state.ts +43 -0
  318. package/src/hooks/session-subscription-cache.ts +25 -0
  319. package/src/hooks/use-chat-layout-query-state.ts +29 -0
  320. package/src/hooks/use-sender-header-query-state.ts +35 -0
  321. package/src/hooks/use-session-subscription.ts +17 -8
  322. package/src/i18n-resources.ts +44 -0
  323. package/src/i18n.ts +21 -6
  324. package/src/main.tsx +8 -0
  325. package/src/pwa.ts +46 -0
  326. package/src/resources/locales/en.json +729 -24
  327. package/src/resources/locales/zh.json +731 -26
  328. package/src/routes/ChatRoute.scss +105 -7
  329. package/src/routes/ChatRoute.tsx +11 -165
  330. package/src/routes/ChatRouteBottomPanel.tsx +47 -0
  331. package/src/routes/ChatRouteView.tsx +199 -0
  332. package/src/runtime-config.ts +155 -2
  333. package/src/server-connection-history.ts +179 -0
  334. package/src/store/index.ts +40 -0
  335. package/src/styles/global.scss +3 -0
  336. package/src/utils/mobile-viewport.ts +67 -0
  337. package/src/version-compatibility.ts +37 -0
  338. package/src/vite-env.d.ts +9 -0
  339. package/src/ws.ts +20 -9
  340. package/vite.config.ts +23 -1
  341. package/dist/assets/channel-Dnopc5A6.js +0 -1
  342. package/dist/assets/clone-sQthahUA.js +0 -1
  343. package/dist/assets/flowDiagram-v2-4f6560a1-OazrdWQO.js +0 -1
  344. package/dist/assets/index-o93dlo92.css +0 -32
  345. package/src/components/chat/NewSessionGuideCompactPanel.tsx +0 -130
  346. package/src/components/chat/NewSessionGuideGrid.tsx +0 -141
  347. package/src/components/chat/sender/@components/reference-actions/ReferencePermissionActionsPopover.tsx +0 -114
@@ -1,40 +1,77 @@
1
1
  import './ConfigView.scss'
2
2
 
3
- import { App, Empty, Select, Space, Spin, Tabs } from 'antd'
3
+ import { App, Button, Empty, Input, Space, Spin, Tooltip } from 'antd'
4
4
  import { useEffect, useMemo, useRef, useState } from 'react'
5
5
  import { useTranslation } from 'react-i18next'
6
6
  import useSWR from 'swr'
7
7
 
8
8
  import type { ConfigSource } from '@vibe-forge/core'
9
- import type { AboutInfo, ConfigResponse } from '@vibe-forge/types'
9
+ import type { AboutInfo, ConfigResponse, ConfigUiSection } from '@vibe-forge/types'
10
10
 
11
+ import { useMobileSidebarModal } from '#~/components/layout/@hooks/use-mobile-sidebar-modal'
11
12
  import { PageShell } from '#~/components/layout/PageShell'
12
13
  import { useResponsiveLayout } from '#~/hooks/use-responsive-layout'
13
14
 
14
- import { getApiErrorMessage, getConfig, updateConfig } from '../api'
15
+ import { getApiErrorMessage, getConfig, getConfigSchema, listWorktreeEnvironments, updateConfig } from '../api'
15
16
  import { useQueryParams } from '../hooks/useQueryParams'
16
17
  import { AboutSection, ConfigSectionPanel, ConfigSourceSwitch, DisplayValue } from './config'
17
18
  import { AppSettingsPanel } from './config/AppSettingsPanel'
19
+ import { WorktreeEnvironmentPanel } from './config/WorktreeEnvironmentPanel'
20
+ import {
21
+ getConfigDraftKey,
22
+ resolveRemoteConfigChangeAction,
23
+ serializeComparableConfigValue
24
+ } from './config/configConflict'
18
25
  import { cloneValue, getValueByPath, isEmptyValue } from './config/configUtils'
26
+ import { toDisplayEnvironmentName, toEnvironmentReference } from './config/worktree-environment-panel-model'
27
+
28
+ interface ConfigDraftConflict {
29
+ draftKey: string
30
+ sectionKey: string
31
+ source: ConfigSource
32
+ remoteValue: unknown
33
+ }
19
34
 
20
35
  export function ConfigView() {
21
36
  const { t } = useTranslation()
22
- const { message } = App.useApp()
37
+ const { message, modal } = App.useApp()
23
38
  const { isCompactLayout, isTouchInteraction } = useResponsiveLayout()
24
39
  const { data, isLoading, error, mutate } = useSWR<ConfigResponse>('/api/config', getConfig)
25
- const { values: queryValues, update: updateQuery, searchParams } = useQueryParams<{ tab: string; source: string }>({
26
- keys: ['tab', 'source'],
27
- defaults: { tab: 'general', source: 'project' }
40
+ const { data: schemaData } = useSWR('/api/config/schema', getConfigSchema)
41
+ const { data: worktreeEnvironmentData } = useSWR('worktree-environments', listWorktreeEnvironments)
42
+ const { values: queryValues, update: updateQuery, searchParams } = useQueryParams<{
43
+ tab: string
44
+ source: string
45
+ detail: string
46
+ }>({
47
+ keys: ['tab', 'source', 'detail'],
48
+ defaults: { tab: 'general', source: 'project', detail: '' },
49
+ omit: {
50
+ detail: value => value.trim() === ''
51
+ }
28
52
  })
29
- const sourceKey: ConfigSource = queryValues.source === 'user' ? 'user' : 'project'
30
- const setSourceKey = (next: ConfigSource) => updateQuery({ source: next })
53
+ const querySourceKey: ConfigSource = queryValues.source === 'user' ? 'user' : 'project'
54
+ const [sourceKey, setSourceKeyState] = useState<ConfigSource>(querySourceKey)
55
+ const [detailQuery, setDetailQueryState] = useState(queryValues.detail)
56
+ const [navSearchQuery, setNavSearchQuery] = useState('')
57
+ const [isSidebarCollapsed, setIsSidebarCollapsed] = useState(false)
58
+ const [isMobileSidebarOpen, setIsMobileSidebarOpen] = useState(false)
31
59
  const [drafts, setDrafts] = useState<Record<string, unknown>>({})
32
60
  const configPresent = data?.meta?.configPresent
33
61
  const currentSource = data?.sources?.[sourceKey]
62
+ const currentResolvedSource = data?.resolvedSources?.[sourceKey]
34
63
  const draftsRef = useRef<Record<string, unknown>>(drafts)
64
+ const compactContentRegionRef = useRef<HTMLDivElement | null>(null)
65
+ const compactSidebarSheetRef = useRef<HTMLDivElement | null>(null)
35
66
  const saveTimersRef = useRef<Record<string, ReturnType<typeof setTimeout>>>({})
36
67
  const savingRef = useRef<Record<string, boolean>>({})
37
68
  const lastSavedRef = useRef<Record<string, string>>({})
69
+ const baseSnapshotsRef = useRef<Record<string, string>>({})
70
+ const blockedDraftKeysRef = useRef<Record<string, boolean>>({})
71
+ const pendingConflictsRef = useRef<Record<string, ConfigDraftConflict>>({})
72
+ const compactSidebarBackgroundRefs = useMemo(() => [compactContentRegionRef], [])
73
+ const [pendingConflicts, setPendingConflicts] = useState<Record<string, ConfigDraftConflict>>({})
74
+ const [activeConflictKey, setActiveConflictKey] = useState<string | null>(null)
38
75
  const mergedModelServices = useMemo(() => data?.sources?.merged?.modelServices ?? {}, [
39
76
  data?.sources?.merged?.modelServices
40
77
  ])
@@ -73,6 +110,11 @@ export function ConfigView() {
73
110
  label: t('config.sections.conversation'),
74
111
  value: currentSource?.conversation
75
112
  },
113
+ {
114
+ key: 'worktreeEnvironments',
115
+ icon: 'deployed_code',
116
+ label: t('config.sections.environments')
117
+ },
76
118
  {
77
119
  key: 'models',
78
120
  icon: 'tune',
@@ -106,36 +148,94 @@ export function ConfigView() {
106
148
  { key: 'about', icon: 'info', label: t('config.sections.about'), value: data?.meta?.about }
107
149
  ], [currentSource, data?.meta?.about, data?.meta?.experiments, t])
108
150
  const tabKeys = useMemo(() => new Set(tabs.filter(tab => tab.type !== 'group').map(tab => tab.key)), [tabs])
151
+ const desktopNavGroups = useMemo(() => {
152
+ type NavTab = Exclude<(typeof tabs)[number], { type: 'group' }>
153
+ interface NavGroup {
154
+ key: string
155
+ label: string
156
+ tabs: NavTab[]
157
+ }
109
158
 
110
- const activeTabKey = tabKeys.has(queryValues.tab) ? queryValues.tab : 'general'
111
- const setActiveTabKey = (key: string) => updateQuery({ tab: key })
112
- const isCompactView = isCompactLayout || isTouchInteraction
113
-
114
- const activeTab = useMemo(() => tabs.find(tab => tab.key === activeTabKey), [tabs, activeTabKey])
115
- const compactTabOptions = useMemo(() => {
116
- const groups: Array<{ label: string; options: Array<{ label: string; value: string }> }> = []
117
- let currentGroup: { label: string; options: Array<{ label: string; value: string }> } | null = null
159
+ const query = navSearchQuery.trim().toLowerCase()
160
+ const groups: NavGroup[] = []
161
+ let currentGroup: NavGroup | null = null
118
162
 
119
163
  tabs.forEach((tab) => {
120
164
  if (tab.type === 'group') {
121
- currentGroup = { label: String(tab.label), options: [] }
122
- groups.push(currentGroup)
165
+ if (currentGroup != null && currentGroup.tabs.length > 0) {
166
+ groups.push(currentGroup)
167
+ }
168
+ currentGroup = { key: tab.key, label: String(tab.label), tabs: [] }
123
169
  return
124
170
  }
125
171
 
126
172
  if (currentGroup == null) {
127
- currentGroup = { label: t('config.groups.config'), options: [] }
128
- groups.push(currentGroup)
173
+ currentGroup = { key: 'group-config', label: t('config.groups.config'), tabs: [] }
129
174
  }
175
+ const targetGroup = currentGroup
176
+ const navTab = tab as NavTab
130
177
 
131
- currentGroup.options.push({
132
- value: tab.key,
133
- label: String(tab.label)
134
- })
178
+ const label = String(tab.label)
179
+ const matches = query === '' ||
180
+ label.toLowerCase().includes(query) ||
181
+ tab.key.toLowerCase().includes(query)
182
+
183
+ if (matches) {
184
+ targetGroup.tabs.push(navTab)
185
+ }
135
186
  })
136
187
 
137
- return groups
138
- }, [tabs, t])
188
+ if (currentGroup != null) {
189
+ groups.push(currentGroup)
190
+ }
191
+
192
+ return groups.filter(group => group.tabs.length > 0)
193
+ }, [navSearchQuery, t, tabs])
194
+
195
+ const queryTabKey = tabKeys.has(queryValues.tab) ? queryValues.tab : 'general'
196
+ const [activeTabKey, setActiveTabKeyState] = useState(queryTabKey)
197
+ const setSourceKey = (next: ConfigSource) => {
198
+ setSourceKeyState(next)
199
+ updateQuery({ source: next })
200
+ }
201
+ const setDetailQuery = (next: string) => {
202
+ setDetailQueryState(next)
203
+ updateQuery({ detail: next })
204
+ }
205
+ const setActiveTabKey = (key: string) => {
206
+ setActiveTabKeyState(key)
207
+ setDetailQueryState('')
208
+ updateQuery({ tab: key, detail: '' })
209
+ }
210
+ const isCompactView = isCompactLayout || isTouchInteraction
211
+ const resolveTooltipTitle = (title: string) => isTouchInteraction ? undefined : title
212
+
213
+ const activeTab = useMemo(() => tabs.find(tab => tab.key === activeTabKey), [tabs, activeTabKey])
214
+ const uiSections = schemaData?.workspace.uiSchema?.sections ?? {}
215
+ const sourceOptions = useMemo(() => [
216
+ {
217
+ value: 'project' as const,
218
+ icon: 'folder',
219
+ label: configPresent?.project === true
220
+ ? t('config.sources.project')
221
+ : t('config.sources.projectMissing')
222
+ },
223
+ {
224
+ value: 'user' as const,
225
+ icon: 'person',
226
+ label: configPresent?.user === true
227
+ ? t('config.sources.user')
228
+ : t('config.sources.userMissing')
229
+ }
230
+ ], [configPresent?.project, configPresent?.user, t])
231
+
232
+ useMobileSidebarModal({
233
+ backgroundRefs: compactSidebarBackgroundRefs,
234
+ isCompactLayout: isCompactView,
235
+ isMobileSidebarOpen,
236
+ setIsMobileSidebarOpen,
237
+ sheetRef: compactSidebarSheetRef
238
+ })
139
239
 
140
240
  useEffect(() => {
141
241
  if (activeTab == null) return
@@ -154,10 +254,36 @@ export function ConfigView() {
154
254
  })
155
255
  }, [activeTab, configTabKeys, sourceKey])
156
256
 
257
+ useEffect(() => {
258
+ setSourceKeyState(querySourceKey)
259
+ }, [querySourceKey])
260
+
261
+ useEffect(() => {
262
+ setActiveTabKeyState(queryTabKey)
263
+ }, [queryTabKey])
264
+
265
+ useEffect(() => {
266
+ setDetailQueryState(queryValues.detail)
267
+ }, [queryValues.detail])
268
+
269
+ useEffect(() => {
270
+ if (!isCompactView) return
271
+ setIsSidebarCollapsed(false)
272
+ }, [isCompactView])
273
+
274
+ useEffect(() => {
275
+ if (isCompactView) return
276
+ setIsMobileSidebarOpen(false)
277
+ }, [isCompactView])
278
+
157
279
  useEffect(() => {
158
280
  draftsRef.current = drafts
159
281
  }, [drafts])
160
282
 
283
+ useEffect(() => {
284
+ pendingConflictsRef.current = pendingConflicts
285
+ }, [pendingConflicts])
286
+
161
287
  useEffect(() => {
162
288
  return () => {
163
289
  Object.values(saveTimersRef.current).forEach((timer) => {
@@ -166,40 +292,103 @@ export function ConfigView() {
166
292
  }
167
293
  }, [])
168
294
 
169
- const getDraftKey = (sectionKey: string, source = sourceKey) => `${source}:${sectionKey}`
295
+ const clearSaveTimer = (draftKey: string) => {
296
+ const timer = saveTimersRef.current[draftKey]
297
+ if (timer == null) return
298
+ clearTimeout(timer)
299
+ delete saveTimersRef.current[draftKey]
300
+ }
301
+
302
+ const clearDraftConflict = (draftKey: string) => {
303
+ delete blockedDraftKeysRef.current[draftKey]
304
+ setPendingConflicts((prev) => {
305
+ if (prev[draftKey] == null) return prev
306
+ const next = { ...prev }
307
+ delete next[draftKey]
308
+ return next
309
+ })
310
+ setActiveConflictKey(prev => prev === draftKey ? null : prev)
311
+ }
312
+
313
+ const getDraftKey = (sectionKey: string, source = sourceKey) => getConfigDraftKey(sectionKey, source)
170
314
  const generalDraftValue = useMemo(() => {
171
315
  const draftKey = getDraftKey('general')
172
316
  return (drafts[draftKey] ?? cloneValue(currentSource?.general ?? {}) ?? {}) as Record<string, unknown>
173
317
  }, [drafts, currentSource?.general, sourceKey])
174
318
  const selectedModelService = (() => {
175
319
  const value = getValueByPath(generalDraftValue, ['defaultModelService'])
176
- return typeof value === 'string' ? value : undefined
320
+ if (typeof value === 'string' && value !== '') return value
321
+ const fallbackValue = getValueByPath(currentResolvedSource?.general, ['defaultModelService'])
322
+ return typeof fallbackValue === 'string' && fallbackValue !== '' ? fallbackValue : undefined
177
323
  })()
324
+ const worktreeEnvironmentOptions = useMemo(() => (
325
+ worktreeEnvironmentData?.environments.map(environment => ({
326
+ value: toEnvironmentReference(environment),
327
+ label: `${toDisplayEnvironmentName(environment.id)} (${
328
+ environment.isLocal
329
+ ? t('config.environments.sources.user')
330
+ : t('config.environments.sources.project')
331
+ })`
332
+ })) ?? []
333
+ ), [t, worktreeEnvironmentData?.environments])
334
+
335
+ const persistDraftValue = async ({
336
+ draftKey,
337
+ sectionKey,
338
+ source,
339
+ value
340
+ }: {
341
+ draftKey: string
342
+ sectionKey: string
343
+ source: ConfigSource
344
+ value: unknown
345
+ }) => {
346
+ const serialized = serializeComparableConfigValue(value)
347
+
348
+ if (savingRef.current[draftKey]) {
349
+ throw new Error(`config draft ${draftKey} is already saving`)
350
+ }
351
+
352
+ savingRef.current[draftKey] = true
353
+ try {
354
+ await updateConfig(source, sectionKey, value)
355
+ lastSavedRef.current[draftKey] = serialized
356
+ baseSnapshotsRef.current[draftKey] = serialized
357
+ await mutate()
358
+ } catch (error) {
359
+ void message.error(getApiErrorMessage(error, t('config.saveFailed')))
360
+ throw error
361
+ } finally {
362
+ savingRef.current[draftKey] = false
363
+ }
364
+ }
178
365
 
179
366
  const scheduleSave = (sectionKey: string, source: ConfigSource, nextValue: unknown) => {
180
367
  const draftKey = getDraftKey(sectionKey, source)
181
- const serialized = JSON.stringify(nextValue ?? {})
182
- if (lastSavedRef.current[draftKey] === serialized) {
368
+ if (blockedDraftKeysRef.current[draftKey]) {
369
+ clearSaveTimer(draftKey)
183
370
  return
184
371
  }
185
- if (saveTimersRef.current[draftKey]) {
186
- clearTimeout(saveTimersRef.current[draftKey])
372
+
373
+ const serialized = serializeComparableConfigValue(nextValue)
374
+ if (lastSavedRef.current[draftKey] === serialized) {
375
+ return
187
376
  }
377
+ clearSaveTimer(draftKey)
188
378
  saveTimersRef.current[draftKey] = setTimeout(async () => {
379
+ if (blockedDraftKeysRef.current[draftKey]) return
189
380
  if (savingRef.current[draftKey]) return
190
381
  const currentValue = draftsRef.current[draftKey] ?? nextValue
191
- const currentSerialized = JSON.stringify(currentValue ?? {})
382
+ const currentSerialized = serializeComparableConfigValue(currentValue)
192
383
  if (lastSavedRef.current[draftKey] === currentSerialized) return
193
- savingRef.current[draftKey] = true
194
384
  try {
195
- await updateConfig(source, sectionKey, currentValue)
196
- lastSavedRef.current[draftKey] = currentSerialized
197
- await mutate()
198
- } catch (error) {
199
- void message.error(getApiErrorMessage(error, t('config.saveFailed')))
200
- } finally {
201
- savingRef.current[draftKey] = false
202
- }
385
+ await persistDraftValue({
386
+ draftKey,
387
+ sectionKey,
388
+ source,
389
+ value: currentValue
390
+ })
391
+ } catch {}
203
392
  }, 800)
204
393
  }
205
394
 
@@ -209,52 +398,327 @@ export function ConfigView() {
209
398
  scheduleSave(sectionKey, sourceKey, nextValue)
210
399
  }
211
400
 
212
- const renderTabContent = (tab: typeof tabs[number]) => (
213
- <div className='config-view__content'>
401
+ useEffect(() => {
402
+ const nextDrafts: Record<string, unknown> = {}
403
+ let hasDraftUpdates = false
404
+ const previousConflicts = pendingConflictsRef.current
405
+ let nextConflicts = previousConflicts
406
+ let conflictsChanged = false
407
+ const ensureMutableConflicts = () => {
408
+ if (!conflictsChanged) {
409
+ nextConflicts = { ...previousConflicts }
410
+ conflictsChanged = true
411
+ }
412
+ return nextConflicts
413
+ }
414
+ ;(['project', 'user'] as const).forEach((source) => {
415
+ const sourceData = data?.sources?.[source]
416
+ if (sourceData == null) return
417
+
418
+ configTabKeys.forEach((sectionKey) => {
419
+ const draftKey = getConfigDraftKey(sectionKey, source)
420
+ const serverValue = cloneValue((sourceData as Record<string, unknown>)[sectionKey] ?? {}) ?? {}
421
+ const serverSerialized = serializeComparableConfigValue(serverValue)
422
+ const baseSerialized = baseSnapshotsRef.current[draftKey]
423
+
424
+ if (baseSerialized == null) {
425
+ baseSnapshotsRef.current[draftKey] = serverSerialized
426
+ lastSavedRef.current[draftKey] ??= serverSerialized
427
+ return
428
+ }
429
+
430
+ const currentDraft = draftsRef.current[draftKey]
431
+ if (currentDraft === undefined) {
432
+ baseSnapshotsRef.current[draftKey] = serverSerialized
433
+ lastSavedRef.current[draftKey] = serverSerialized
434
+ delete blockedDraftKeysRef.current[draftKey]
435
+ if (nextConflicts[draftKey] != null) {
436
+ delete ensureMutableConflicts()[draftKey]
437
+ }
438
+ return
439
+ }
440
+
441
+ const draftSerialized = serializeComparableConfigValue(currentDraft)
442
+ const action = resolveRemoteConfigChangeAction({
443
+ baseSerialized,
444
+ draftSerialized,
445
+ serverSerialized
446
+ })
447
+
448
+ if (action === 'sync-remote') {
449
+ clearSaveTimer(draftKey)
450
+ delete blockedDraftKeysRef.current[draftKey]
451
+ baseSnapshotsRef.current[draftKey] = serverSerialized
452
+ lastSavedRef.current[draftKey] = serverSerialized
453
+ nextDrafts[draftKey] = serverValue
454
+ hasDraftUpdates = true
455
+ if (nextConflicts[draftKey] != null) {
456
+ delete ensureMutableConflicts()[draftKey]
457
+ }
458
+ return
459
+ }
460
+
461
+ if (action === 'conflict') {
462
+ clearSaveTimer(draftKey)
463
+ blockedDraftKeysRef.current[draftKey] = true
464
+ const existingConflict = nextConflicts[draftKey]
465
+ const existingRemoteSerialized = existingConflict == null
466
+ ? undefined
467
+ : serializeComparableConfigValue(existingConflict.remoteValue)
468
+ if (
469
+ existingConflict?.sectionKey !== sectionKey ||
470
+ existingConflict?.source !== source ||
471
+ existingRemoteSerialized !== serverSerialized
472
+ ) {
473
+ ensureMutableConflicts()[draftKey] = {
474
+ draftKey,
475
+ sectionKey,
476
+ source,
477
+ remoteValue: serverValue
478
+ }
479
+ }
480
+ return
481
+ }
482
+
483
+ if (draftSerialized === serverSerialized) {
484
+ baseSnapshotsRef.current[draftKey] = serverSerialized
485
+ lastSavedRef.current[draftKey] = serverSerialized
486
+ }
487
+
488
+ delete blockedDraftKeysRef.current[draftKey]
489
+ if (nextConflicts[draftKey] != null) {
490
+ delete ensureMutableConflicts()[draftKey]
491
+ }
492
+ })
493
+ })
494
+
495
+ if (conflictsChanged) {
496
+ setPendingConflicts(nextConflicts)
497
+ }
498
+
499
+ if (!hasDraftUpdates) return
500
+
501
+ setDrafts((prev) => {
502
+ let changed = false
503
+ const next = { ...prev }
504
+ Object.entries(nextDrafts).forEach(([draftKey, value]) => {
505
+ const currentSerialized = serializeComparableConfigValue(prev[draftKey])
506
+ const nextSerialized = serializeComparableConfigValue(value)
507
+ if (currentSerialized === nextSerialized) return
508
+ next[draftKey] = value
509
+ changed = true
510
+ })
511
+ return changed ? next : prev
512
+ })
513
+ }, [configTabKeys, data?.sources?.project, data?.sources?.user])
514
+
515
+ useEffect(() => {
516
+ if (activeConflictKey != null) return
517
+
518
+ const nextConflict = Object.values(pendingConflicts)[0]
519
+ if (nextConflict == null) return
520
+
521
+ const draftKey = nextConflict.draftKey
522
+ setActiveConflictKey(draftKey)
523
+
524
+ const sourceLabel = t(`config.sources.${nextConflict.source}`)
525
+ const sectionLabel = t(`config.sections.${nextConflict.sectionKey}`, { defaultValue: nextConflict.sectionKey })
526
+
527
+ modal.confirm({
528
+ title: t('config.conflict.title'),
529
+ content: (
530
+ <div>
531
+ <div>
532
+ {t('config.conflict.description', {
533
+ source: sourceLabel,
534
+ target: sectionLabel
535
+ })}
536
+ </div>
537
+ <div>{t('config.conflict.instructions')}</div>
538
+ </div>
539
+ ),
540
+ okText: t('config.conflict.keepLocal'),
541
+ cancelText: t('config.conflict.useRemote'),
542
+ cancelButtonProps: { danger: true },
543
+ closable: false,
544
+ keyboard: false,
545
+ maskClosable: false,
546
+ onOk: async () => {
547
+ const currentConflict = pendingConflictsRef.current[draftKey]
548
+ const sectionKey = currentConflict?.sectionKey ?? nextConflict.sectionKey
549
+ const source = currentConflict?.source ?? nextConflict.source
550
+ const currentDraft = cloneValue(
551
+ draftsRef.current[draftKey] ?? currentConflict?.remoteValue ?? nextConflict.remoteValue ?? {}
552
+ ) ?? {}
553
+
554
+ await persistDraftValue({
555
+ draftKey,
556
+ sectionKey,
557
+ source,
558
+ value: currentDraft
559
+ })
560
+
561
+ clearDraftConflict(draftKey)
562
+ },
563
+ onCancel: () => {
564
+ const currentConflict = pendingConflictsRef.current[draftKey] ?? nextConflict
565
+ const remoteValue = cloneValue(currentConflict.remoteValue ?? {}) ?? {}
566
+ const remoteSerialized = serializeComparableConfigValue(remoteValue)
567
+
568
+ clearSaveTimer(draftKey)
569
+ baseSnapshotsRef.current[draftKey] = remoteSerialized
570
+ lastSavedRef.current[draftKey] = remoteSerialized
571
+ setDrafts(prev => ({ ...prev, [draftKey]: remoteValue }))
572
+ clearDraftConflict(draftKey)
573
+ }
574
+ })
575
+ }, [activeConflictKey, modal, pendingConflicts, t])
576
+
577
+ const renderSidebarExpandButton = () => (
578
+ <Tooltip title={resolveTooltipTitle(t('common.expand'))} placement='bottom'>
579
+ <Button
580
+ size='small'
581
+ type='text'
582
+ className='config-view__section-toggle'
583
+ aria-label={t('common.expand')}
584
+ icon={<span className='material-symbols-rounded'>left_panel_open</span>}
585
+ onClick={() => {
586
+ if (isCompactView) {
587
+ setIsMobileSidebarOpen(true)
588
+ return
589
+ }
590
+ setIsSidebarCollapsed(false)
591
+ }}
592
+ />
593
+ </Tooltip>
594
+ )
595
+
596
+ const renderStandaloneHeader = (showSidebarToggle: boolean) => {
597
+ if (!showSidebarToggle) return null
598
+ return (
599
+ <div className='config-view__standalone-header'>
600
+ {renderSidebarExpandButton()}
601
+ </div>
602
+ )
603
+ }
604
+
605
+ const renderSidebar = ({ compact = false }: { compact?: boolean } = {}) => (
606
+ <div className={`config-view__sidebar ${compact ? 'config-view__sidebar--compact' : ''}`}>
607
+ <div className='config-view__sidebar-header'>
608
+ <div className='config-view__sidebar-search-row'>
609
+ <Input
610
+ allowClear
611
+ value={navSearchQuery}
612
+ onChange={(event) => setNavSearchQuery(event.target.value)}
613
+ prefix={<span className='material-symbols-rounded config-view__sidebar-search-icon'>search</span>}
614
+ placeholder={t('config.navigation.search')}
615
+ className='config-view__sidebar-search-input'
616
+ />
617
+ <Tooltip
618
+ title={resolveTooltipTitle(compact ? t('common.close') : t('common.collapse'))}
619
+ placement='bottom'
620
+ >
621
+ <Button
622
+ type='text'
623
+ className='config-view__sidebar-toggle'
624
+ aria-label={compact ? t('common.close') : t('common.collapse')}
625
+ onClick={() => {
626
+ if (compact) {
627
+ setIsMobileSidebarOpen(false)
628
+ return
629
+ }
630
+ setIsSidebarCollapsed(true)
631
+ }}
632
+ icon={<span className='material-symbols-rounded'>{compact ? 'close' : 'left_panel_close'}</span>}
633
+ />
634
+ </Tooltip>
635
+ </div>
636
+ </div>
637
+ <div className='config-view__sidebar-body'>
638
+ {desktopNavGroups.length === 0
639
+ ? (
640
+ <div className='config-view__sidebar-empty'>{t('config.navigation.noResults')}</div>
641
+ )
642
+ : desktopNavGroups.map(group => (
643
+ <div key={group.key} className='config-view__nav-group'>
644
+ <div className='config-view__nav-group-label'>{group.label}</div>
645
+ <div className='config-view__nav-list'>
646
+ {group.tabs.map(tab => (
647
+ <button
648
+ key={tab.key}
649
+ type='button'
650
+ className={`config-view__nav-item ${activeTabKey === tab.key ? 'is-active' : ''}`}
651
+ onClick={() => {
652
+ setActiveTabKey(tab.key)
653
+ if (compact) {
654
+ setIsMobileSidebarOpen(false)
655
+ }
656
+ }}
657
+ >
658
+ <span className='config-view__tab-label'>
659
+ <span className='material-symbols-rounded config-view__tab-icon'>{tab.icon}</span>
660
+ <span className='config-view__tab-text'>{tab.label}</span>
661
+ </span>
662
+ </button>
663
+ ))}
664
+ </div>
665
+ </div>
666
+ ))}
667
+ </div>
668
+ </div>
669
+ )
670
+
671
+ const renderTabContent = (
672
+ tab: typeof tabs[number],
673
+ { showSidebarToggle = false }: { showSidebarToggle?: boolean } = {}
674
+ ) => (
675
+ <div key={`${sourceKey}:${tab.key}`} className='config-view__content'>
676
+ {renderStandaloneHeader(showSidebarToggle && !configTabKeys.has(tab.key))}
214
677
  {tab.key === 'about' && (
215
678
  <AboutSection value={tab.value as AboutInfo | undefined} />
216
679
  )}
217
680
  {tab.key === 'appearance' && (
218
681
  <AppSettingsPanel t={t} />
219
682
  )}
220
- {tab.key !== 'about' && tab.key !== 'appearance' && !configTabKeys.has(tab.key) && (
221
- <DisplayValue value={tab.value} sectionKey={tab.key} t={t} />
683
+ {tab.key === 'worktreeEnvironments' && (
684
+ <WorktreeEnvironmentPanel t={t} />
222
685
  )}
686
+ {tab.key !== 'about' &&
687
+ tab.key !== 'appearance' &&
688
+ tab.key !== 'worktreeEnvironments' &&
689
+ !configTabKeys.has(tab.key) && (
690
+ <DisplayValue value={tab.value} sectionKey={tab.key} t={t} />
691
+ )}
223
692
  {configTabKeys.has(tab.key) && (
224
693
  <ConfigSectionPanel
225
694
  sectionKey={tab.key}
695
+ title={tab.label}
696
+ icon={tab.icon}
697
+ uiSection={uiSections[tab.key] as ConfigUiSection | undefined}
226
698
  value={drafts[getDraftKey(tab.key)] ?? cloneValue(tab.value ?? {}) ?? {}}
699
+ resolvedValue={cloneValue(
700
+ currentResolvedSource != null
701
+ ? (currentResolvedSource as Record<string, unknown>)[tab.key]
702
+ : undefined
703
+ ) ?? {}}
227
704
  onChange={(next) => handleDraftChange(tab.key, next)}
228
705
  mergedModelServices={mergedModelServices as Record<string, unknown>}
229
706
  mergedAdapters={mergedAdapters as Record<string, unknown>}
230
707
  selectedModelService={selectedModelService}
708
+ worktreeEnvironmentOptions={worktreeEnvironmentOptions}
709
+ detailQuery={activeTabKey === tab.key ? detailQuery : ''}
710
+ onDetailQueryChange={activeTabKey === tab.key ? setDetailQuery : undefined}
231
711
  t={t}
232
- headerExtra={isCompactView
233
- ? undefined
234
- : (
235
- <Space size={12}>
236
- <ConfigSourceSwitch
237
- value={sourceKey}
238
- onChange={setSourceKey}
239
- options={[
240
- {
241
- value: 'project',
242
- icon: 'folder',
243
- label: configPresent?.project === true
244
- ? t('config.sources.project')
245
- : t('config.sources.projectMissing')
246
- },
247
- {
248
- value: 'user',
249
- icon: 'person',
250
- label: configPresent?.user === true
251
- ? t('config.sources.user')
252
- : t('config.sources.userMissing')
253
- }
254
- ]}
255
- />
256
- </Space>
257
- )}
712
+ headerLeading={showSidebarToggle ? renderSidebarExpandButton() : undefined}
713
+ headerExtra={
714
+ <Space size={12}>
715
+ <ConfigSourceSwitch
716
+ value={sourceKey}
717
+ onChange={setSourceKey}
718
+ options={sourceOptions}
719
+ />
720
+ </Space>
721
+ }
258
722
  />
259
723
  )}
260
724
  </div>
@@ -279,76 +743,42 @@ export function ConfigView() {
279
743
  isCompactView
280
744
  ? (
281
745
  <div className='config-view__compact-shell'>
282
- <div className='config-view__compact-controls'>
283
- <Select
284
- value={activeTabKey}
285
- onChange={setActiveTabKey}
286
- options={compactTabOptions}
287
- className='config-view__compact-select'
288
- popupMatchSelectWidth={false}
289
- />
290
- {activeTab != null && configTabKeys.has(activeTab.key) && (
291
- <ConfigSourceSwitch
292
- value={sourceKey}
293
- onChange={setSourceKey}
294
- options={[
295
- {
296
- value: 'project',
297
- icon: 'folder',
298
- label: configPresent?.project === true
299
- ? t('config.sources.project')
300
- : t('config.sources.projectMissing')
301
- },
302
- {
303
- value: 'user',
304
- icon: 'person',
305
- label: configPresent?.user === true
306
- ? t('config.sources.user')
307
- : t('config.sources.userMissing')
308
- }
309
- ]}
310
- />
311
- )}
746
+ <div
747
+ ref={compactContentRegionRef}
748
+ className='config-view__compact-region'
749
+ aria-hidden={isMobileSidebarOpen ? true : undefined}
750
+ >
751
+ <div className='config-view__compact-panel'>
752
+ {activeTab != null ? renderTabContent(activeTab, { showSidebarToggle: true }) : null}
753
+ </div>
312
754
  </div>
313
- <div className='config-view__compact-panel'>
314
- {activeTab != null ? renderTabContent(activeTab) : null}
755
+ <button
756
+ type='button'
757
+ className={`config-view__compact-backdrop ${isMobileSidebarOpen ? 'is-open' : ''}`}
758
+ aria-label={t('common.close')}
759
+ aria-hidden={!isMobileSidebarOpen}
760
+ tabIndex={-1}
761
+ onClick={() => setIsMobileSidebarOpen(false)}
762
+ />
763
+ <div
764
+ ref={compactSidebarSheetRef}
765
+ className={`config-view__compact-sidebar-sheet ${isMobileSidebarOpen ? 'is-open' : ''}`}
766
+ role='dialog'
767
+ aria-modal={isMobileSidebarOpen ? 'true' : undefined}
768
+ aria-label={t('common.settings')}
769
+ aria-hidden={!isMobileSidebarOpen}
770
+ tabIndex={-1}
771
+ >
772
+ {renderSidebar({ compact: true })}
315
773
  </div>
316
774
  </div>
317
775
  )
318
776
  : (
319
- <div className='config-view__tabs-wrap'>
320
- <Tabs
321
- tabPosition='left'
322
- tabBarGutter={4}
323
- indicator={{ size: 0 }}
324
- className='config-view__tabs'
325
- activeKey={activeTabKey}
326
- onChange={(key) => {
327
- if (key !== 'group-config' && key !== 'group-app') {
328
- setActiveTabKey(key)
329
- }
330
- }}
331
- items={tabs.map((tab) => {
332
- if (tab.type === 'group') {
333
- return {
334
- key: tab.key,
335
- label: <span className='config-view__group-label'>{tab.label}</span>,
336
- disabled: true,
337
- children: <div />
338
- }
339
- }
340
- return {
341
- key: tab.key,
342
- label: (
343
- <span className='config-view__tab-label'>
344
- <span className='material-symbols-rounded config-view__tab-icon'>{tab.icon}</span>
345
- <span className='config-view__tab-text'>{tab.label}</span>
346
- </span>
347
- ),
348
- children: renderTabContent(tab)
349
- }
350
- })}
351
- />
777
+ <div className={`config-view__desktop-shell ${isSidebarCollapsed ? 'is-sidebar-collapsed' : ''}`}>
778
+ {renderSidebar()}
779
+ <div className='config-view__desktop-content'>
780
+ {activeTab != null ? renderTabContent(activeTab, { showSidebarToggle: isSidebarCollapsed }) : null}
781
+ </div>
352
782
  </div>
353
783
  )
354
784
  )}