@vibe-forge/client 2.0.0 → 3.0.0-alpha.0

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 (351) hide show
  1. package/AGENTS.md +4 -1
  2. package/dist/assets/{arc-CbOXL0l9.js → arc-1JbypnRY.js} +1 -1
  3. package/dist/assets/{blockDiagram-c4efeb88-CqxINvsS.js → blockDiagram-c4efeb88-jT5b9nId.js} +1 -1
  4. package/dist/assets/{c4Diagram-c83219d4-BKazU0hb.js → c4Diagram-c83219d4-KNPh-1ta.js} +1 -1
  5. package/dist/assets/channel-pzvjJnfd.js +1 -0
  6. package/dist/assets/{classDiagram-beda092f-fAFX5BpB.js → classDiagram-beda092f-vvXuOT3F.js} +1 -1
  7. package/dist/assets/{classDiagram-v2-2358418a-w1VkNGJj.js → classDiagram-v2-2358418a-vH4j7skr.js} +1 -1
  8. package/dist/assets/clone-CtWwIeUb.js +1 -0
  9. package/dist/assets/{createText-1719965b-CEinakVP.js → createText-1719965b-COeSYnf3.js} +1 -1
  10. package/dist/assets/{cssMode-DPqRki4y.js → cssMode-Doc7QRKn.js} +1 -1
  11. package/dist/assets/{edges-96097737-Cb0F1_3K.js → edges-96097737-D09v6R_r.js} +1 -1
  12. package/dist/assets/{erDiagram-0228fc6a-C-N2fx-J.js → erDiagram-0228fc6a-CbZ6rfxl.js} +1 -1
  13. package/dist/assets/{flowDb-c6c81e3f-D1Xz_8Gf.js → flowDb-c6c81e3f-CnZOzhdk.js} +1 -1
  14. package/dist/assets/{flowDiagram-50d868cf-DyPSZyAj.js → flowDiagram-50d868cf-jv-xWM2p.js} +1 -1
  15. package/dist/assets/flowDiagram-v2-4f6560a1-BiyrkIKs.js +1 -0
  16. package/dist/assets/{flowchart-elk-definition-6af322e1-Dr1DDXwE.js → flowchart-elk-definition-6af322e1-D8pf677G.js} +1 -1
  17. package/dist/assets/{freemarker2-C3DvPFaK.js → freemarker2-CL0o23Gj.js} +1 -1
  18. package/dist/assets/{ganttDiagram-a2739b55-DmvY1GRj.js → ganttDiagram-a2739b55-BAPQzC4u.js} +1 -1
  19. package/dist/assets/{gitGraphDiagram-82fe8481-CoXfPYYi.js → gitGraphDiagram-82fe8481-BI2x71m0.js} +1 -1
  20. package/dist/assets/{graph-BkDQy7Qt.js → graph-CZK4Bjpq.js} +1 -1
  21. package/dist/assets/{handlebars-BcTFdqjl.js → handlebars-CCG38Pg6.js} +1 -1
  22. package/dist/assets/{html-Dg-O6XFr.js → html-BddshTWG.js} +1 -1
  23. package/dist/assets/{htmlMode-B_wqYWvn.js → htmlMode-xKXiYcDz.js} +1 -1
  24. package/dist/assets/{index-5325376f-kxPTR3_e.js → index-5325376f-BWMTD8RU.js} +1 -1
  25. package/dist/assets/{index-wkhI4dr6.js → index-CBe7kDkV.js} +398 -377
  26. package/dist/assets/index-MWOwVzqE.css +32 -0
  27. package/dist/assets/{infoDiagram-8eee0895-BEvqkwPI.js → infoDiagram-8eee0895-CR78btIF.js} +1 -1
  28. package/dist/assets/{javascript-DhlOH8_z.js → javascript-BPlRHPjg.js} +1 -1
  29. package/dist/assets/{journeyDiagram-c64418c1-gKtLYmmp.js → journeyDiagram-c64418c1-DZRv6FKz.js} +1 -1
  30. package/dist/assets/{jsonMode-DxTbF9OD.js → jsonMode-BYLVfdkf.js} +1 -1
  31. package/dist/assets/{layout-CDaZEk6E.js → layout-BtudyPU2.js} +1 -1
  32. package/dist/assets/{line-DNRQu8iq.js → line-DgHXrIhS.js} +1 -1
  33. package/dist/assets/{linear-Cph9Z6_j.js → linear-DtBoKICx.js} +1 -1
  34. package/dist/assets/{liquid-ByZ6JgRG.js → liquid-DxBlJk0W.js} +1 -1
  35. package/dist/assets/{lspLanguageFeatures-DzvhkgnM.js → lspLanguageFeatures-DmKVpmeH.js} +1 -1
  36. package/dist/assets/{mdx-D8RGHTl6.js → mdx-DrScsd-w.js} +1 -1
  37. package/dist/assets/{mermaid.core-BgcryF__.js → mermaid.core-Cj_NJ_lZ.js} +4 -4
  38. package/dist/assets/{mindmap-definition-8da855dc-WrxK0FcB.js → mindmap-definition-8da855dc-CMXbwtsc.js} +1 -1
  39. package/dist/assets/{pieDiagram-a8764435-VsZBsiQy.js → pieDiagram-a8764435-Bd6rfpJv.js} +1 -1
  40. package/dist/assets/{python-CXVtk_cg.js → python-Crbi7B7n.js} +1 -1
  41. package/dist/assets/{quadrantDiagram-1e28029f-BVlgwOvU.js → quadrantDiagram-1e28029f-H-FdBQn6.js} +1 -1
  42. package/dist/assets/{razor-0tind7h2.js → razor-DHyrzIfE.js} +1 -1
  43. package/dist/assets/{requirementDiagram-08caed73-CpPMPoYp.js → requirementDiagram-08caed73-BSyCVDSG.js} +1 -1
  44. package/dist/assets/{sankeyDiagram-a04cb91d-Cm5nnRmc.js → sankeyDiagram-a04cb91d-S6E6BReg.js} +1 -1
  45. package/dist/assets/{sequenceDiagram-c5b8d532-DpMlJvJB.js → sequenceDiagram-c5b8d532-BMHY4KMj.js} +1 -1
  46. package/dist/assets/{stateDiagram-1ecb1508-DU1zc7vq.js → stateDiagram-1ecb1508-BMrMw6XR.js} +1 -1
  47. package/dist/assets/{stateDiagram-v2-c2b004d7-D-0RgmAp.js → stateDiagram-v2-c2b004d7-B5SIFUb_.js} +1 -1
  48. package/dist/assets/{styles-b4e223ce-BSO-yNWV.js → styles-b4e223ce-DqiXwnx3.js} +1 -1
  49. package/dist/assets/{styles-ca3715f6-CHnsn2Ro.js → styles-ca3715f6-jNxW2Db8.js} +1 -1
  50. package/dist/assets/{styles-d45a18b0-B-rVGjEq.js → styles-d45a18b0-C4nlfLcZ.js} +1 -1
  51. package/dist/assets/{svgDrawCommon-b86b1483-CA3Pl89f.js → svgDrawCommon-b86b1483-FIWFuZfb.js} +1 -1
  52. package/dist/assets/{timeline-definition-faaaa080-BcihLR6s.js → timeline-definition-faaaa080-CEQDoqdf.js} +1 -1
  53. package/dist/assets/{tsMode-D9GGa5Ur.js → tsMode-DnNy3rE9.js} +1 -1
  54. package/dist/assets/{typescript-BT9CK_EL.js → typescript-YBZ4vw5B.js} +1 -1
  55. package/dist/assets/{xml-DNO75J-T.js → xml-BJGGYD70.js} +1 -1
  56. package/dist/assets/{xychartDiagram-f5964ef8-DJTwe32X.js → xychartDiagram-f5964ef8-DUNOFOk0.js} +1 -1
  57. package/dist/assets/{yaml-7CVzhiP2.js → yaml-BMY4mu5s.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 +596 -28
  85. package/src/components/ConfigView.tsx +569 -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 +110 -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 +220 -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 +211 -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 +130 -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 +254 -123
  119. package/src/components/chat/NewSessionGuide.scss +274 -204
  120. package/src/components/chat/NewSessionGuide.tsx +46 -111
  121. package/src/components/chat/NewSessionGuideStarterList.tsx +190 -0
  122. package/src/components/chat/NewSessionGuideStarterSection.tsx +121 -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 +21 -6
  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 +29 -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 +257 -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 +85 -9
  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/@hooks/use-skills-cli-modal-controller.ts +157 -0
  244. package/src/components/knowledge-base/components/@hooks/use-skills-tab-actions.ts +63 -0
  245. package/src/components/knowledge-base/components/ActionButton.scss +30 -4
  246. package/src/components/knowledge-base/components/ActionButton.tsx +13 -3
  247. package/src/components/knowledge-base/components/CreateSkillModal.tsx +59 -0
  248. package/src/components/knowledge-base/components/EmptyState.scss +4 -2
  249. package/src/components/knowledge-base/components/EntitiesTab.tsx +20 -20
  250. package/src/components/knowledge-base/components/EntityList.scss +3 -0
  251. package/src/components/knowledge-base/components/FilterBar.scss +1 -0
  252. package/src/components/knowledge-base/components/FilterBar.tsx +12 -8
  253. package/src/components/knowledge-base/components/FlowsTab.tsx +20 -20
  254. package/src/components/knowledge-base/components/KnowledgeBaseHeader.tsx +7 -6
  255. package/src/components/knowledge-base/components/KnowledgeContentControls.tsx +35 -0
  256. package/src/components/knowledge-base/components/KnowledgeList.scss +14 -3
  257. package/src/components/knowledge-base/components/KnowledgeMobilePanel.tsx +122 -0
  258. package/src/components/knowledge-base/components/KnowledgeSidebar.tsx +97 -0
  259. package/src/components/knowledge-base/components/LoadingState.scss +2 -1
  260. package/src/components/knowledge-base/components/ProjectSkillsList.tsx +79 -0
  261. package/src/components/knowledge-base/components/RuleList.scss +3 -0
  262. package/src/components/knowledge-base/components/RulesTab.tsx +31 -30
  263. package/src/components/knowledge-base/components/SectionHeader.scss +13 -1
  264. package/src/components/knowledge-base/components/SectionHeader.tsx +5 -3
  265. package/src/components/knowledge-base/components/SkillArchiveInput.tsx +43 -0
  266. package/src/components/knowledge-base/components/SkillHubResultItem.tsx +112 -0
  267. package/src/components/knowledge-base/components/SkillMarketResults.tsx +98 -0
  268. package/src/components/knowledge-base/components/SkillMarketView.tsx +198 -0
  269. package/src/components/knowledge-base/components/SkillMarketView.types.ts +28 -0
  270. package/src/components/knowledge-base/components/SkillRegistryErrors.tsx +21 -0
  271. package/src/components/knowledge-base/components/SkillRegistryModal.tsx +74 -0
  272. package/src/components/knowledge-base/components/SkillsCliModal.tsx +154 -0
  273. package/src/components/knowledge-base/components/SkillsTab.scss +424 -0
  274. package/src/components/knowledge-base/components/SkillsTab.tsx +166 -35
  275. package/src/components/knowledge-base/components/SkillsTabActions.tsx +88 -0
  276. package/src/components/knowledge-base/components/SpecList.scss +3 -0
  277. package/src/components/knowledge-base/components/TabContent.scss +4 -3
  278. package/src/components/knowledge-base/components/skill-hub-utils.ts +108 -0
  279. package/src/components/knowledge-base/components/use-skill-market-filters.ts +37 -0
  280. package/src/components/knowledge-base/components/use-skill-market-query-input.ts +44 -0
  281. package/src/components/knowledge-base/components/use-skill-market-search.ts +49 -0
  282. package/src/components/knowledge-base/components/use-skill-registry-modal.ts +68 -0
  283. package/src/components/monaco/monaco-runtime.ts +44 -0
  284. package/src/components/monaco/use-monaco-theme.ts +63 -0
  285. package/src/components/nav-rail-account-actions.tsx +104 -0
  286. package/src/components/server-connection/ServerConnectionGate.scss +356 -0
  287. package/src/components/server-connection/ServerConnectionGate.tsx +238 -0
  288. package/src/components/server-connection/ServerConnectionProfileModal.tsx +145 -0
  289. package/src/components/server-connection/ServerConnectionProfiles.tsx +113 -0
  290. package/src/components/server-connection/ServerConnectionUrlInput.tsx +85 -0
  291. package/src/components/sidebar/SidebarHeader.scss +5 -41
  292. package/src/components/sidebar/SidebarHeader.tsx +117 -91
  293. package/src/components/sidebar/SidebarHeaderSearchActions.tsx +24 -28
  294. package/src/components/sidebar/sidebar-search-visibility.ts +18 -0
  295. package/src/components/sidebar-list/SidebarListHeader.scss +246 -0
  296. package/src/components/sidebar-list/SidebarListHeader.tsx +146 -0
  297. package/src/components/workspace/ContextFilePicker.scss +9 -26
  298. package/src/components/workspace/ContextFilePicker.tsx +31 -113
  299. package/src/components/workspace/context-file-types.ts +36 -0
  300. package/src/components/workspace/project-file-tree/ProjectFileTree.scss +298 -0
  301. package/src/components/workspace/project-file-tree/ProjectFileTree.tsx +138 -0
  302. package/src/components/workspace/project-file-tree/ProjectFileTreeRow.tsx +167 -0
  303. package/src/components/workspace/project-file-tree/ProjectFileTreeRowContextMenu.tsx +106 -0
  304. package/src/components/workspace/project-file-tree/ProjectFileTreeRows.tsx +139 -0
  305. package/src/components/workspace/project-file-tree/project-file-tree-helpers.ts +101 -0
  306. package/src/components/workspace/project-file-tree/project-file-tree-icons.ts +93 -0
  307. package/src/components/workspace/project-file-tree/project-file-tree-types.ts +27 -0
  308. package/src/components/workspace/project-file-tree/use-project-file-tree-data.ts +197 -0
  309. package/src/components/workspace/project-file-tree/use-project-file-tree-selection.ts +144 -0
  310. package/src/hooks/chat/chat-session-target.ts +69 -0
  311. package/src/hooks/chat/chat-session-workspace-draft.ts +11 -4
  312. package/src/hooks/chat/interaction-state.ts +1 -0
  313. package/src/hooks/chat/optimistic-session-creation.ts +189 -0
  314. package/src/hooks/chat/use-chat-adapter-account-selection.tsx +156 -0
  315. package/src/hooks/chat/use-chat-route-bottom-panel.ts +181 -0
  316. package/src/hooks/chat/use-chat-route-deep-link-view.ts +33 -0
  317. package/src/hooks/chat/use-chat-session-actions.ts +259 -65
  318. package/src/hooks/chat/use-chat-session-messages.ts +71 -4
  319. package/src/hooks/chat/use-chat-session.ts +36 -1
  320. package/src/hooks/chat/workspace-file-panel-state.ts +43 -0
  321. package/src/hooks/session-subscription-cache.ts +25 -0
  322. package/src/hooks/use-chat-layout-query-state.ts +29 -0
  323. package/src/hooks/use-sender-header-query-state.ts +35 -0
  324. package/src/hooks/use-session-subscription.ts +17 -8
  325. package/src/hooks/useQueryParams.ts +91 -23
  326. package/src/i18n-resources.ts +44 -0
  327. package/src/i18n.ts +21 -6
  328. package/src/main.tsx +8 -0
  329. package/src/pwa.ts +46 -0
  330. package/src/resources/locales/en.json +741 -24
  331. package/src/resources/locales/zh.json +743 -26
  332. package/src/routes/ChatRoute.scss +110 -7
  333. package/src/routes/ChatRoute.tsx +11 -165
  334. package/src/routes/ChatRouteBottomPanel.tsx +47 -0
  335. package/src/routes/ChatRouteView.tsx +199 -0
  336. package/src/runtime-config.ts +155 -2
  337. package/src/server-connection-history.ts +179 -0
  338. package/src/store/index.ts +151 -3
  339. package/src/styles/global.scss +3 -0
  340. package/src/utils/mobile-viewport.ts +67 -0
  341. package/src/version-compatibility.ts +37 -0
  342. package/src/vite-env.d.ts +9 -0
  343. package/src/ws.ts +20 -9
  344. package/vite.config.ts +23 -1
  345. package/dist/assets/channel-Dnopc5A6.js +0 -1
  346. package/dist/assets/clone-sQthahUA.js +0 -1
  347. package/dist/assets/flowDiagram-v2-4f6560a1-OazrdWQO.js +0 -1
  348. package/dist/assets/index-o93dlo92.css +0 -32
  349. package/src/components/chat/NewSessionGuideCompactPanel.tsx +0 -130
  350. package/src/components/chat/NewSessionGuideGrid.tsx +0 -141
  351. 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 {
20
+ getConfigDraftKey,
21
+ resolveRemoteConfigChangeAction,
22
+ serializeComparableConfigValue
23
+ } from './config/configConflict'
24
+ import { WorktreeEnvironmentPanel } from './config/WorktreeEnvironmentPanel'
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,328 @@ 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
+
415
+ ;(['project', 'user'] as const).forEach((source) => {
416
+ const sourceData = data?.sources?.[source]
417
+ if (sourceData == null) return
418
+
419
+ configTabKeys.forEach((sectionKey) => {
420
+ const draftKey = getConfigDraftKey(sectionKey, source)
421
+ const serverValue = cloneValue((sourceData as Record<string, unknown>)[sectionKey] ?? {}) ?? {}
422
+ const serverSerialized = serializeComparableConfigValue(serverValue)
423
+ const baseSerialized = baseSnapshotsRef.current[draftKey]
424
+
425
+ if (baseSerialized == null) {
426
+ baseSnapshotsRef.current[draftKey] = serverSerialized
427
+ lastSavedRef.current[draftKey] ??= serverSerialized
428
+ return
429
+ }
430
+
431
+ const currentDraft = draftsRef.current[draftKey]
432
+ if (currentDraft === undefined) {
433
+ baseSnapshotsRef.current[draftKey] = serverSerialized
434
+ lastSavedRef.current[draftKey] = serverSerialized
435
+ delete blockedDraftKeysRef.current[draftKey]
436
+ if (nextConflicts[draftKey] != null) {
437
+ delete ensureMutableConflicts()[draftKey]
438
+ }
439
+ return
440
+ }
441
+
442
+ const draftSerialized = serializeComparableConfigValue(currentDraft)
443
+ const action = resolveRemoteConfigChangeAction({
444
+ baseSerialized,
445
+ draftSerialized,
446
+ serverSerialized
447
+ })
448
+
449
+ if (action === 'sync-remote') {
450
+ clearSaveTimer(draftKey)
451
+ delete blockedDraftKeysRef.current[draftKey]
452
+ baseSnapshotsRef.current[draftKey] = serverSerialized
453
+ lastSavedRef.current[draftKey] = serverSerialized
454
+ nextDrafts[draftKey] = serverValue
455
+ hasDraftUpdates = true
456
+ if (nextConflicts[draftKey] != null) {
457
+ delete ensureMutableConflicts()[draftKey]
458
+ }
459
+ return
460
+ }
461
+
462
+ if (action === 'conflict') {
463
+ clearSaveTimer(draftKey)
464
+ blockedDraftKeysRef.current[draftKey] = true
465
+ const existingConflict = nextConflicts[draftKey]
466
+ const existingRemoteSerialized = existingConflict == null
467
+ ? undefined
468
+ : serializeComparableConfigValue(existingConflict.remoteValue)
469
+ if (
470
+ existingConflict?.sectionKey !== sectionKey ||
471
+ existingConflict?.source !== source ||
472
+ existingRemoteSerialized !== serverSerialized
473
+ ) {
474
+ ensureMutableConflicts()[draftKey] = {
475
+ draftKey,
476
+ sectionKey,
477
+ source,
478
+ remoteValue: serverValue
479
+ }
480
+ }
481
+ return
482
+ }
483
+
484
+ if (draftSerialized === serverSerialized) {
485
+ baseSnapshotsRef.current[draftKey] = serverSerialized
486
+ lastSavedRef.current[draftKey] = serverSerialized
487
+ }
488
+
489
+ delete blockedDraftKeysRef.current[draftKey]
490
+ if (nextConflicts[draftKey] != null) {
491
+ delete ensureMutableConflicts()[draftKey]
492
+ }
493
+ })
494
+ })
495
+
496
+ if (conflictsChanged) {
497
+ setPendingConflicts(nextConflicts)
498
+ }
499
+
500
+ if (!hasDraftUpdates) return
501
+
502
+ setDrafts((prev) => {
503
+ let changed = false
504
+ const next = { ...prev }
505
+ Object.entries(nextDrafts).forEach(([draftKey, value]) => {
506
+ const currentSerialized = serializeComparableConfigValue(prev[draftKey])
507
+ const nextSerialized = serializeComparableConfigValue(value)
508
+ if (currentSerialized === nextSerialized) return
509
+ next[draftKey] = value
510
+ changed = true
511
+ })
512
+ return changed ? next : prev
513
+ })
514
+ }, [configTabKeys, data?.sources?.project, data?.sources?.user])
515
+
516
+ useEffect(() => {
517
+ if (activeConflictKey != null) return
518
+
519
+ const nextConflict = Object.values(pendingConflicts)[0]
520
+ if (nextConflict == null) return
521
+
522
+ const draftKey = nextConflict.draftKey
523
+ setActiveConflictKey(draftKey)
524
+
525
+ const sourceLabel = t(`config.sources.${nextConflict.source}`)
526
+ const sectionLabel = t(`config.sections.${nextConflict.sectionKey}`, { defaultValue: nextConflict.sectionKey })
527
+
528
+ modal.confirm({
529
+ title: t('config.conflict.title'),
530
+ content: (
531
+ <div>
532
+ <div>
533
+ {t('config.conflict.description', {
534
+ source: sourceLabel,
535
+ target: sectionLabel
536
+ })}
537
+ </div>
538
+ <div>{t('config.conflict.instructions')}</div>
539
+ </div>
540
+ ),
541
+ okText: t('config.conflict.keepLocal'),
542
+ cancelText: t('config.conflict.useRemote'),
543
+ cancelButtonProps: { danger: true },
544
+ closable: false,
545
+ keyboard: false,
546
+ maskClosable: false,
547
+ onOk: async () => {
548
+ const currentConflict = pendingConflictsRef.current[draftKey]
549
+ const sectionKey = currentConflict?.sectionKey ?? nextConflict.sectionKey
550
+ const source = currentConflict?.source ?? nextConflict.source
551
+ const currentDraft = cloneValue(
552
+ draftsRef.current[draftKey] ?? currentConflict?.remoteValue ?? nextConflict.remoteValue ?? {}
553
+ ) ?? {}
554
+
555
+ await persistDraftValue({
556
+ draftKey,
557
+ sectionKey,
558
+ source,
559
+ value: currentDraft
560
+ })
561
+
562
+ clearDraftConflict(draftKey)
563
+ },
564
+ onCancel: () => {
565
+ const currentConflict = pendingConflictsRef.current[draftKey] ?? nextConflict
566
+ const remoteValue = cloneValue(currentConflict.remoteValue ?? {}) ?? {}
567
+ const remoteSerialized = serializeComparableConfigValue(remoteValue)
568
+
569
+ clearSaveTimer(draftKey)
570
+ baseSnapshotsRef.current[draftKey] = remoteSerialized
571
+ lastSavedRef.current[draftKey] = remoteSerialized
572
+ setDrafts(prev => ({ ...prev, [draftKey]: remoteValue }))
573
+ clearDraftConflict(draftKey)
574
+ }
575
+ })
576
+ }, [activeConflictKey, modal, pendingConflicts, t])
577
+
578
+ const renderSidebarExpandButton = () => (
579
+ <Tooltip title={resolveTooltipTitle(t('common.expand'))} placement='bottom'>
580
+ <Button
581
+ size='small'
582
+ type='text'
583
+ className='config-view__section-toggle'
584
+ aria-label={t('common.expand')}
585
+ icon={<span className='material-symbols-rounded'>left_panel_open</span>}
586
+ onClick={() => {
587
+ if (isCompactView) {
588
+ setIsMobileSidebarOpen(true)
589
+ return
590
+ }
591
+ setIsSidebarCollapsed(false)
592
+ }}
593
+ />
594
+ </Tooltip>
595
+ )
596
+
597
+ const renderStandaloneHeader = (showSidebarToggle: boolean) => {
598
+ if (!showSidebarToggle) return null
599
+ return (
600
+ <div className='config-view__standalone-header'>
601
+ {renderSidebarExpandButton()}
602
+ </div>
603
+ )
604
+ }
605
+
606
+ const renderSidebar = ({ compact = false }: { compact?: boolean } = {}) => (
607
+ <div className={`config-view__sidebar ${compact ? 'config-view__sidebar--compact' : ''}`}>
608
+ <div className='config-view__sidebar-header'>
609
+ <div className='config-view__sidebar-search-row'>
610
+ <Input
611
+ allowClear
612
+ value={navSearchQuery}
613
+ onChange={(event) => setNavSearchQuery(event.target.value)}
614
+ prefix={<span className='material-symbols-rounded config-view__sidebar-search-icon'>search</span>}
615
+ placeholder={t('config.navigation.search')}
616
+ className='config-view__sidebar-search-input'
617
+ />
618
+ <Tooltip
619
+ title={resolveTooltipTitle(compact ? t('common.close') : t('common.collapse'))}
620
+ placement='bottom'
621
+ >
622
+ <Button
623
+ type='text'
624
+ className='config-view__sidebar-toggle'
625
+ aria-label={compact ? t('common.close') : t('common.collapse')}
626
+ onClick={() => {
627
+ if (compact) {
628
+ setIsMobileSidebarOpen(false)
629
+ return
630
+ }
631
+ setIsSidebarCollapsed(true)
632
+ }}
633
+ icon={<span className='material-symbols-rounded'>{compact ? 'close' : 'left_panel_close'}</span>}
634
+ />
635
+ </Tooltip>
636
+ </div>
637
+ </div>
638
+ <div className='config-view__sidebar-body'>
639
+ {desktopNavGroups.length === 0
640
+ ? (
641
+ <div className='config-view__sidebar-empty'>{t('config.navigation.noResults')}</div>
642
+ )
643
+ : desktopNavGroups.map(group => (
644
+ <div key={group.key} className='config-view__nav-group'>
645
+ <div className='config-view__nav-group-label'>{group.label}</div>
646
+ <div className='config-view__nav-list'>
647
+ {group.tabs.map(tab => (
648
+ <button
649
+ key={tab.key}
650
+ type='button'
651
+ className={`config-view__nav-item ${activeTabKey === tab.key ? 'is-active' : ''}`}
652
+ onClick={() => {
653
+ setActiveTabKey(tab.key)
654
+ if (compact) {
655
+ setIsMobileSidebarOpen(false)
656
+ }
657
+ }}
658
+ >
659
+ <span className='config-view__tab-label'>
660
+ <span className='material-symbols-rounded config-view__tab-icon'>{tab.icon}</span>
661
+ <span className='config-view__tab-text'>{tab.label}</span>
662
+ </span>
663
+ </button>
664
+ ))}
665
+ </div>
666
+ </div>
667
+ ))}
668
+ </div>
669
+ </div>
670
+ )
671
+
672
+ const renderTabContent = (
673
+ tab: typeof tabs[number],
674
+ { showSidebarToggle = false }: { showSidebarToggle?: boolean } = {}
675
+ ) => (
676
+ <div key={`${sourceKey}:${tab.key}`} className='config-view__content'>
677
+ {renderStandaloneHeader(showSidebarToggle && !configTabKeys.has(tab.key))}
214
678
  {tab.key === 'about' && (
215
679
  <AboutSection value={tab.value as AboutInfo | undefined} />
216
680
  )}
217
681
  {tab.key === 'appearance' && (
218
682
  <AppSettingsPanel t={t} />
219
683
  )}
220
- {tab.key !== 'about' && tab.key !== 'appearance' && !configTabKeys.has(tab.key) && (
221
- <DisplayValue value={tab.value} sectionKey={tab.key} t={t} />
684
+ {tab.key === 'worktreeEnvironments' && (
685
+ <WorktreeEnvironmentPanel t={t} />
222
686
  )}
687
+ {tab.key !== 'about' &&
688
+ tab.key !== 'appearance' &&
689
+ tab.key !== 'worktreeEnvironments' &&
690
+ !configTabKeys.has(tab.key) && (
691
+ <DisplayValue value={tab.value} sectionKey={tab.key} t={t} />
692
+ )}
223
693
  {configTabKeys.has(tab.key) && (
224
694
  <ConfigSectionPanel
225
695
  sectionKey={tab.key}
696
+ title={tab.label}
697
+ icon={tab.icon}
698
+ uiSection={uiSections[tab.key] as ConfigUiSection | undefined}
226
699
  value={drafts[getDraftKey(tab.key)] ?? cloneValue(tab.value ?? {}) ?? {}}
700
+ resolvedValue={cloneValue(
701
+ currentResolvedSource != null
702
+ ? (currentResolvedSource as Record<string, unknown>)[tab.key]
703
+ : undefined
704
+ ) ?? {}}
227
705
  onChange={(next) => handleDraftChange(tab.key, next)}
228
706
  mergedModelServices={mergedModelServices as Record<string, unknown>}
229
707
  mergedAdapters={mergedAdapters as Record<string, unknown>}
230
708
  selectedModelService={selectedModelService}
709
+ worktreeEnvironmentOptions={worktreeEnvironmentOptions}
710
+ detailQuery={activeTabKey === tab.key ? detailQuery : ''}
711
+ onDetailQueryChange={activeTabKey === tab.key ? setDetailQuery : undefined}
231
712
  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
- )}
713
+ headerLeading={showSidebarToggle ? renderSidebarExpandButton() : undefined}
714
+ headerExtra={
715
+ <Space size={12}>
716
+ <ConfigSourceSwitch
717
+ value={sourceKey}
718
+ onChange={setSourceKey}
719
+ options={sourceOptions}
720
+ />
721
+ </Space>
722
+ }
258
723
  />
259
724
  )}
260
725
  </div>
@@ -279,76 +744,42 @@ export function ConfigView() {
279
744
  isCompactView
280
745
  ? (
281
746
  <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
- )}
747
+ <div
748
+ ref={compactContentRegionRef}
749
+ className='config-view__compact-region'
750
+ aria-hidden={isMobileSidebarOpen ? true : undefined}
751
+ >
752
+ <div className='config-view__compact-panel'>
753
+ {activeTab != null ? renderTabContent(activeTab, { showSidebarToggle: true }) : null}
754
+ </div>
312
755
  </div>
313
- <div className='config-view__compact-panel'>
314
- {activeTab != null ? renderTabContent(activeTab) : null}
756
+ <button
757
+ type='button'
758
+ className={`config-view__compact-backdrop ${isMobileSidebarOpen ? 'is-open' : ''}`}
759
+ aria-label={t('common.close')}
760
+ aria-hidden={!isMobileSidebarOpen}
761
+ tabIndex={-1}
762
+ onClick={() => setIsMobileSidebarOpen(false)}
763
+ />
764
+ <div
765
+ ref={compactSidebarSheetRef}
766
+ className={`config-view__compact-sidebar-sheet ${isMobileSidebarOpen ? 'is-open' : ''}`}
767
+ role='dialog'
768
+ aria-modal={isMobileSidebarOpen ? 'true' : undefined}
769
+ aria-label={t('common.settings')}
770
+ aria-hidden={!isMobileSidebarOpen}
771
+ tabIndex={-1}
772
+ >
773
+ {renderSidebar({ compact: true })}
315
774
  </div>
316
775
  </div>
317
776
  )
318
777
  : (
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
- />
778
+ <div className={`config-view__desktop-shell ${isSidebarCollapsed ? 'is-sidebar-collapsed' : ''}`}>
779
+ {renderSidebar()}
780
+ <div className='config-view__desktop-content'>
781
+ {activeTab != null ? renderTabContent(activeTab, { showSidebarToggle: isSidebarCollapsed }) : null}
782
+ </div>
352
783
  </div>
353
784
  )
354
785
  )}