@nextclaw/ui 0.12.9 → 0.12.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (245) hide show
  1. package/CHANGELOG.md +102 -0
  2. package/dist/assets/ChannelsList-SQ7Oxotv.js +8 -0
  3. package/dist/assets/DocBrowser-BCO2k6XD.js +1 -0
  4. package/dist/assets/{DocBrowser-6ReNjvzF.js → DocBrowser-rDOjI3ga.js} +1 -1
  5. package/dist/assets/{DocBrowserContext-B6SpA7Qs.js → DocBrowserContext-BUq3Wo8O.js} +1 -1
  6. package/dist/assets/{LogoBadge-ByNLYg65.js → LogoBadge-DP8Ye7wJ.js} +1 -1
  7. package/dist/assets/ModelConfig-C77Ae9ru.js +1 -0
  8. package/dist/assets/ProviderScopedModelInput-CEnK61uo.js +1 -0
  9. package/dist/assets/ProvidersList-BCupBayq.js +1 -0
  10. package/dist/assets/RuntimeConfig-Ad-CAcmy.js +1 -0
  11. package/dist/assets/SearchConfig-BfCz4wJ4.js +1 -0
  12. package/dist/assets/SecretsConfig-DjmBIhyy.js +3 -0
  13. package/dist/assets/{SessionsConfig-ChHQ7M5c.js → SessionsConfig-CvjxU40H.js} +2 -2
  14. package/dist/assets/{book-open-BdcxxoQu.js → book-open-BE8M56IM.js} +1 -1
  15. package/dist/assets/chat-page-JKC6ln-y.js +58 -0
  16. package/dist/assets/chat-session-display-YcRMrAMa.js +1 -0
  17. package/dist/assets/{chunk-JZWAC4HX-DK5HPmIK.js → chunk-JZWAC4HX-erTUn3b8.js} +1 -1
  18. package/dist/assets/client-CszWMVKi.js +7 -0
  19. package/dist/assets/config-split-page-BAGSzUR3.js +1 -0
  20. package/dist/assets/{createLucideIcon-BSeTgkZW.js → createLucideIcon-CCiTGX8L.js} +1 -1
  21. package/dist/assets/desktop-DfkLlkG2.js +1 -0
  22. package/dist/assets/desktop-update-config-BXeGlqHD.js +1 -0
  23. package/dist/assets/dialog-BghZFPch.js +5 -0
  24. package/dist/assets/{dist-6TrrnPCR.js → dist-Dd9cr-kz.js} +1 -1
  25. package/dist/assets/dist-ZwoAXs46.js +9 -0
  26. package/dist/assets/{download-BhDxnyvU.js → download-D7LOizcW.js} +1 -1
  27. package/dist/assets/es2015-CEAreese.js +41 -0
  28. package/dist/assets/{external-link-BgErLCNT.js → external-link-qsnCMhw1.js} +1 -1
  29. package/dist/assets/{hash-Bl7dr_UG.js → hash-0zjWsNl-.js} +1 -1
  30. package/dist/assets/{i18n-eDHeDY0n.js → i18n-DvzXOGQX.js} +1 -1
  31. package/dist/assets/index-DvVTC9FF.css +1 -0
  32. package/dist/assets/index-lr6rQUSd.js +2 -0
  33. package/dist/assets/key-round-BLe9D8ND.js +1 -0
  34. package/dist/assets/loader-circle-wj7kARHv.js +1 -0
  35. package/dist/assets/{logos-x89HbrZ4.js → logos-_v5b2SdG.js} +1 -1
  36. package/dist/assets/marketplace-page-CAAk1Khc.js +1 -0
  37. package/dist/assets/marketplace-page-CfCiq90S.js +49 -0
  38. package/dist/assets/mcp-marketplace-page-D0Pp9Hs-.js +40 -0
  39. package/dist/assets/play-o6NmwGTi.js +1 -0
  40. package/dist/assets/plus-I9pBS4Fl.js +1 -0
  41. package/dist/assets/{refresh-cw-C47QSEwg.js → refresh-cw-MNqgR3LZ.js} +1 -1
  42. package/dist/assets/remote-C9fXm4V5.js +1 -0
  43. package/dist/assets/{save-3S6-H3Xw.js → save-D4bObrmH.js} +1 -1
  44. package/dist/assets/search-DxmL3IWE.js +1 -0
  45. package/dist/assets/security-config-BUm6FFfl.js +1 -0
  46. package/dist/assets/select-BILPf7zs.js +1 -0
  47. package/dist/assets/setting-row-BATDgg4r.js +1 -0
  48. package/dist/assets/skeleton-COKMAnJy.js +1 -0
  49. package/dist/assets/{switch-BsLtHOH-.js → switch-CBOzecWS.js} +1 -1
  50. package/dist/assets/{tabs-custom-D3HYMt6k.js → tabs-custom-Bx3cNhD-.js} +1 -1
  51. package/dist/assets/tag-chip-zUaDE2-H.js +1 -0
  52. package/dist/assets/{trash-2-G48scll7.js → trash-2-CQUgYyRn.js} +1 -1
  53. package/dist/assets/use-infinite-scroll-loader-B5V2Klve.js +1 -0
  54. package/dist/assets/useConfirmDialog-patAnl1g.js +1 -0
  55. package/dist/assets/{useMutation-CBWjE2uj.js → useMutation-__AYv-Pz.js} +1 -1
  56. package/dist/assets/x-BHUGQIUv.js +1 -0
  57. package/dist/index.html +22 -22
  58. package/dist/runtime-icons/claude.ico +0 -0
  59. package/dist/runtime-icons/codex-openai.svg +6 -0
  60. package/dist/runtime-icons/hermes-agent.png +0 -0
  61. package/module-structure.config.json +7 -0
  62. package/package.json +6 -6
  63. package/public/runtime-icons/claude.ico +0 -0
  64. package/public/runtime-icons/codex-openai.svg +6 -0
  65. package/public/runtime-icons/hermes-agent.png +0 -0
  66. package/src/api/chat-session-type.types.ts +7 -0
  67. package/src/api/config.ts +10 -0
  68. package/src/api/raw-client.test.ts +1 -1
  69. package/src/api/{raw-client.ts → raw-client.utils.ts} +2 -0
  70. package/src/api/runtime-control.types.ts +8 -0
  71. package/src/api/types.ts +48 -0
  72. package/src/app/components/app-manager-provider.tsx +20 -0
  73. package/src/app/managers/app.manager.ts +12 -0
  74. package/src/app.tsx +223 -59
  75. package/src/components/agents/agent-dialogs.tsx +499 -0
  76. package/src/components/agents/agents-page.test.tsx +238 -0
  77. package/src/components/agents/agents-page.tsx +435 -0
  78. package/src/components/chat/chat-conversation-panel.test.tsx +30 -0
  79. package/src/components/chat/chat-conversation-panel.tsx +83 -13
  80. package/src/components/chat/chat-input/ncp-chat-input-availability.utils.test.ts +92 -0
  81. package/src/components/chat/chat-input/ncp-chat-input-availability.utils.ts +45 -0
  82. package/src/components/chat/chat-page-shell.tsx +19 -13
  83. package/src/components/chat/chat-session-type-option-item.test.tsx +46 -0
  84. package/src/components/chat/chat-session-type-option-item.tsx +68 -0
  85. package/src/components/chat/chat-session-workspace-file-preview.test.tsx +87 -0
  86. package/src/components/chat/chat-session-workspace-file-preview.tsx +14 -43
  87. package/src/components/chat/chat-session-workspace-panel-nav.tsx +8 -2
  88. package/src/components/chat/chat-sidebar-project-groups.tsx +11 -36
  89. package/src/components/chat/containers/chat-input-bar.container.tsx +24 -12
  90. package/src/components/chat/{ChatSidebar.test.tsx → containers/chat-sidebar.test.tsx} +5 -4
  91. package/src/components/chat/{ChatSidebar.tsx → containers/chat-sidebar.tsx} +24 -72
  92. package/src/components/chat/hooks/use-chat-sidebar-session-label-editor.ts +49 -0
  93. package/src/components/chat/ncp/__tests__/ncp-session-adapter.cancelled-tool.test.ts +77 -0
  94. package/src/components/chat/ncp/ncp-app-client-fetch.ts +3 -0
  95. package/src/components/chat/ncp/ncp-chat-input.manager.ts +13 -5
  96. package/src/components/chat/ncp/ncp-chat-page.tsx +23 -2
  97. package/src/components/chat/ncp/ncp-session-adapter.test.ts +1 -0
  98. package/src/components/chat/ncp/ncp-session-adapter.ts +3 -0
  99. package/src/components/chat/ncp/page/ncp-chat-derived-state.ts +10 -4
  100. package/src/components/chat/ncp/session-conversation/use-ncp-session-conversation.test.tsx +48 -4
  101. package/src/components/chat/ncp/session-conversation/use-ncp-session-conversation.ts +43 -5
  102. package/src/components/chat/ncp/tests/ncp-chat-input.manager.test.ts +51 -1
  103. package/src/components/chat/stores/chat-input.store.ts +2 -1
  104. package/src/components/chat/stores/chat-thread.store.ts +3 -1
  105. package/src/components/chat/useChatSessionTypeState.ts +10 -1
  106. package/src/components/chat/workspace/chat-session-workspace-file-breadcrumbs.tsx +86 -0
  107. package/src/components/common/BrandHeader.tsx +3 -1
  108. package/src/components/common/session-context-icon.tsx +15 -2
  109. package/src/components/common/{TagInput.tsx → tag-input.tsx} +25 -17
  110. package/src/components/config/ChannelForm.test.tsx +89 -3
  111. package/src/components/config/ChannelForm.tsx +157 -188
  112. package/src/components/config/ChannelsList.test.tsx +163 -119
  113. package/src/components/config/ChannelsList.tsx +90 -101
  114. package/src/components/config/ProviderForm.tsx +108 -146
  115. package/src/components/config/ProvidersList.tsx +100 -123
  116. package/src/components/config/SearchConfig.tsx +423 -393
  117. package/src/components/config/channel-form-fields-section.tsx +70 -37
  118. package/src/components/config/config-split-page.tsx +109 -0
  119. package/src/components/config/desktop-update-config.test.tsx +10 -4
  120. package/src/components/config/desktop-update-config.tsx +5 -3
  121. package/src/components/config/provider-enabled-field.tsx +17 -10
  122. package/src/components/config/runtime-control-card.test.tsx +136 -158
  123. package/src/components/config/runtime-control-card.tsx +43 -68
  124. package/src/components/config/runtime-presence-card.test.tsx +10 -14
  125. package/src/components/config/runtime-presence-card.tsx +97 -81
  126. package/src/components/layout/AppLayout.tsx +25 -37
  127. package/src/components/layout/Sidebar.tsx +4 -4
  128. package/src/components/layout/app-layout.test.tsx +46 -14
  129. package/src/components/layout/runtime-status-entry.test.tsx +101 -0
  130. package/src/components/layout/runtime-status-entry.tsx +95 -0
  131. package/src/components/layout/sidebar.layout.test.tsx +11 -5
  132. package/src/components/marketplace/marketplace-detail-doc.ts +93 -0
  133. package/src/components/marketplace/marketplace-list-card.tsx +288 -0
  134. package/src/components/marketplace/marketplace-page-data.ts +129 -0
  135. package/src/components/marketplace/marketplace-page.test.tsx +339 -0
  136. package/src/components/marketplace/marketplace-page.tsx +596 -0
  137. package/src/components/marketplace/mcp/mcp-marketplace-card.tsx +128 -0
  138. package/src/components/marketplace/mcp/mcp-marketplace-dialogs.tsx +191 -0
  139. package/src/components/marketplace/mcp/mcp-marketplace-doc.ts +152 -0
  140. package/src/components/marketplace/mcp/mcp-marketplace-page.test.tsx +223 -0
  141. package/src/components/marketplace/mcp/mcp-marketplace-page.tsx +414 -0
  142. package/src/components/ui/notice-card.tsx +129 -0
  143. package/src/components/ui/setting-row.tsx +51 -0
  144. package/src/components/ui/tag-chip.tsx +39 -0
  145. package/src/components/ui/textarea.tsx +19 -0
  146. package/src/features/account/components/account-panel.tsx +255 -0
  147. package/src/features/account/index.ts +6 -0
  148. package/src/{account → features/account}/managers/account.manager.ts +6 -5
  149. package/src/features/remote/components/remote-access-page.test.tsx +104 -0
  150. package/src/features/remote/components/remote-access-page.tsx +250 -0
  151. package/src/{hooks/useRemoteAccess.ts → features/remote/hooks/use-remote-access.ts} +1 -1
  152. package/src/features/remote/index.ts +27 -0
  153. package/src/{remote → features/remote}/managers/remote-access.manager.ts +3 -4
  154. package/src/{remote → features/remote/services}/remote-access-feedback.service.test.ts +1 -1
  155. package/src/features/system-status/hooks/use-system-status.ts +104 -0
  156. package/src/features/system-status/index.ts +12 -0
  157. package/src/features/system-status/managers/system-status.manager.bootstrap-polling.test.ts +126 -0
  158. package/src/features/system-status/managers/system-status.manager.test.ts +142 -0
  159. package/src/features/system-status/managers/system-status.manager.ts +511 -0
  160. package/src/features/system-status/stores/system-status.store.ts +32 -0
  161. package/src/features/system-status/types/system-status.types.ts +73 -0
  162. package/src/features/system-status/utils/system-status.utils.test.ts +132 -0
  163. package/src/features/system-status/utils/system-status.utils.ts +202 -0
  164. package/src/hooks/use-realtime-query-bridge.ts +34 -18
  165. package/src/hooks/useConfig.ts +2 -1
  166. package/src/index.css +24 -0
  167. package/src/lib/app-resource-uri.test.ts +20 -0
  168. package/src/lib/app-resource-uri.ts +29 -0
  169. package/src/lib/i18n.chat.ts +8 -0
  170. package/src/lib/i18n.remote.ts +1 -1
  171. package/src/lib/i18n.runtime-control.ts +31 -0
  172. package/src/lib/i18n.ts +5 -8
  173. package/src/lib/session-context.utils.test.ts +71 -0
  174. package/src/lib/session-context.utils.ts +28 -3
  175. package/src/lib/session-project/workspace-file-breadcrumb.test.ts +83 -0
  176. package/src/lib/session-project/workspace-file-breadcrumb.ts +188 -0
  177. package/src/platforms/desktop/index.ts +20 -0
  178. package/src/{desktop → platforms/desktop}/managers/desktop-presence.manager.ts +2 -2
  179. package/src/{desktop → platforms/desktop}/managers/desktop-update.manager.ts +2 -2
  180. package/src/{desktop → platforms/desktop}/stores/desktop-presence.store.ts +1 -1
  181. package/src/{desktop → platforms/desktop}/stores/desktop-update.store.ts +1 -1
  182. package/src/stores/ui.store.ts +0 -9
  183. package/src/transport/{app-client.ts → app-client.service.ts} +9 -9
  184. package/src/transport/app-client.test.ts +9 -5
  185. package/src/transport/index.ts +1 -1
  186. package/src/transport/{local.transport.ts → local-transport.service.ts} +14 -12
  187. package/dist/assets/ChannelsList-Ita2Zm1_.js +0 -8
  188. package/dist/assets/DocBrowser-BNwbPHf4.js +0 -1
  189. package/dist/assets/MarketplacePage-CjX2MWww.js +0 -1
  190. package/dist/assets/MarketplacePage-D0sDlYX4.js +0 -49
  191. package/dist/assets/McpMarketplacePage-BGKJm1sJ.js +0 -40
  192. package/dist/assets/ModelConfig-BzZenCH-.js +0 -1
  193. package/dist/assets/ProviderScopedModelInput-Da7khnBA.js +0 -1
  194. package/dist/assets/ProvidersList-BbVzRxjY.js +0 -1
  195. package/dist/assets/RemoteAccessPage-BaDH_X1Q.js +0 -1
  196. package/dist/assets/RuntimeConfig-F_XKGgLm.js +0 -1
  197. package/dist/assets/SearchConfig-BGkzXQP-.js +0 -1
  198. package/dist/assets/SecretsConfig-D281Rotl.js +0 -3
  199. package/dist/assets/app-query-client-VnFElj4E.js +0 -1
  200. package/dist/assets/chat-page-Doe0yTtB.js +0 -58
  201. package/dist/assets/chat-session-display-cw78aiI_.js +0 -1
  202. package/dist/assets/client-_i4MU2bB.js +0 -7
  203. package/dist/assets/config-DtIQwrHF.js +0 -1
  204. package/dist/assets/config-layout-CHs0mAaR.js +0 -1
  205. package/dist/assets/desktop-update-config-Dpcf4BKG.js +0 -1
  206. package/dist/assets/dist-ccBFUi-o.js +0 -9
  207. package/dist/assets/index-CF9xve0E.js +0 -6
  208. package/dist/assets/index-FgA52VBt.css +0 -1
  209. package/dist/assets/infiniteQueryBehavior-ZDS92Qpp.js +0 -1
  210. package/dist/assets/loader-circle-ACM1s51e.js +0 -1
  211. package/dist/assets/page-layout-vZnghcFy.js +0 -1
  212. package/dist/assets/play-CFUwCA2E.js +0 -1
  213. package/dist/assets/plus-rYsv72JG.js +0 -1
  214. package/dist/assets/popover-Bg1VoTZ6.js +0 -1
  215. package/dist/assets/refresh-ccw-DT98i__E.js +0 -1
  216. package/dist/assets/rotate-cw-JtFzpNn6.js +0 -1
  217. package/dist/assets/search-3kFR_zh9.js +0 -1
  218. package/dist/assets/security-config-BWaiARNk.js +0 -1
  219. package/dist/assets/select-DJ2MUjBB.js +0 -41
  220. package/dist/assets/skeleton-ByQepn0M.js +0 -1
  221. package/dist/assets/status-dot-vbanNPFU.js +0 -1
  222. package/dist/assets/use-infinite-scroll-loader-DkNhD-42.js +0 -1
  223. package/dist/assets/useConfirmDialog-BkvTN-vd.js +0 -1
  224. package/dist/assets/x-ByDbItbq.js +0 -1
  225. package/src/account/components/account-panel.tsx +0 -135
  226. package/src/components/agents/AgentDialogs.tsx +0 -400
  227. package/src/components/agents/AgentsPage.test.tsx +0 -217
  228. package/src/components/agents/AgentsPage.tsx +0 -352
  229. package/src/components/config/config-layout.ts +0 -10
  230. package/src/components/marketplace/MarketplacePage.test.tsx +0 -322
  231. package/src/components/marketplace/MarketplacePage.tsx +0 -827
  232. package/src/components/marketplace/mcp/McpMarketplacePage.test.tsx +0 -208
  233. package/src/components/marketplace/mcp/McpMarketplacePage.tsx +0 -580
  234. package/src/components/remote/RemoteAccessPage.test.tsx +0 -103
  235. package/src/components/remote/RemoteAccessPage.tsx +0 -144
  236. package/src/hooks/use-runtime-control.ts +0 -24
  237. package/src/presenter/app-presenter-context.tsx +0 -20
  238. package/src/presenter/app.presenter.ts +0 -12
  239. package/src/runtime-control/runtime-control.manager.ts +0 -118
  240. /package/dist/assets/{config-hints-BhTmc9P1.js → config-hints-DSQQbeOA.js} +0 -0
  241. /package/src/{account → features/account}/stores/account.store.ts +0 -0
  242. /package/src/{remote → features/remote/services}/remote-access-feedback.service.ts +0 -0
  243. /package/src/{remote/remote-access.query.ts → features/remote/services/remote-access-query.service.ts} +0 -0
  244. /package/src/{remote → features/remote}/stores/remote-access.store.ts +0 -0
  245. /package/src/{desktop → platforms/desktop/types}/desktop-update.types.ts +0 -0
@@ -1,29 +1,46 @@
1
- import type { Dispatch, SetStateAction } from 'react';
2
- import { Input } from '@/components/ui/input';
3
- import { Label } from '@/components/ui/label';
4
- import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
5
- import { Switch } from '@/components/ui/switch';
6
- import { TagInput } from '@/components/common/TagInput';
7
- import { hintForPath } from '@/lib/config-hints';
8
- import { t } from '@/lib/i18n';
9
- import { Globe, Hash, KeyRound, Mail, Settings, ToggleLeft } from 'lucide-react';
10
- import type { ConfigUiHints } from '@/api/types';
11
- import type { ChannelField } from './channel-form-fields';
1
+ import type { Dispatch, SetStateAction } from "react";
2
+ import { Input } from "@/components/ui/input";
3
+ import { Label } from "@/components/ui/label";
4
+ import {
5
+ Select,
6
+ SelectContent,
7
+ SelectItem,
8
+ SelectTrigger,
9
+ SelectValue,
10
+ } from "@/components/ui/select";
11
+ import { Switch } from "@/components/ui/switch";
12
+ import { TagInput } from "@/components/common/tag-input";
13
+ import { hintForPath } from "@/lib/config-hints";
14
+ import { t } from "@/lib/i18n";
15
+ import {
16
+ Globe,
17
+ Hash,
18
+ KeyRound,
19
+ Mail,
20
+ Settings,
21
+ ToggleLeft,
22
+ } from "lucide-react";
23
+ import type { ConfigUiHints } from "@/api/types";
24
+ import type { ChannelField } from "./channel-form-fields";
12
25
 
13
26
  function getFieldIcon(fieldName: string) {
14
- if (fieldName.includes('token') || fieldName.includes('secret') || fieldName.includes('password')) {
27
+ if (
28
+ fieldName.includes("token") ||
29
+ fieldName.includes("secret") ||
30
+ fieldName.includes("password")
31
+ ) {
15
32
  return <KeyRound className="h-3.5 w-3.5 text-gray-500" />;
16
33
  }
17
- if (fieldName.includes('url') || fieldName.includes('host')) {
34
+ if (fieldName.includes("url") || fieldName.includes("host")) {
18
35
  return <Globe className="h-3.5 w-3.5 text-gray-500" />;
19
36
  }
20
- if (fieldName.includes('email') || fieldName.includes('mail')) {
37
+ if (fieldName.includes("email") || fieldName.includes("mail")) {
21
38
  return <Mail className="h-3.5 w-3.5 text-gray-500" />;
22
39
  }
23
- if (fieldName.includes('id') || fieldName.includes('from')) {
40
+ if (fieldName.includes("id") || fieldName.includes("from")) {
24
41
  return <Hash className="h-3.5 w-3.5 text-gray-500" />;
25
42
  }
26
- if (fieldName === 'enabled' || fieldName === 'consentGranted') {
43
+ if (fieldName === "enabled" || fieldName === "consentGranted") {
27
44
  return <ToggleLeft className="h-3.5 w-3.5 text-gray-500" />;
28
45
  }
29
46
  return <Settings className="h-3.5 w-3.5 text-gray-500" />;
@@ -46,79 +63,95 @@ export function ChannelFormFieldsSection({
46
63
  jsonDrafts,
47
64
  setJsonDrafts,
48
65
  updateField,
49
- uiHints
66
+ uiHints,
50
67
  }: ChannelFormFieldsSectionProps) {
51
68
  return (
52
69
  <>
53
70
  {fields.map((field) => {
54
- const hint = hintForPath(`channels.${channelName}.${field.name}`, uiHints);
71
+ const hint = hintForPath(
72
+ `channels.${channelName}.${field.name}`,
73
+ uiHints,
74
+ );
55
75
  const label = hint?.label ?? field.label;
56
76
  const placeholder = hint?.placeholder;
57
77
 
58
78
  return (
59
79
  <div key={field.name} className="space-y-2.5">
60
- <Label htmlFor={field.name} className="flex items-center gap-2 text-sm font-medium text-gray-900">
80
+ <Label
81
+ htmlFor={field.name}
82
+ className="flex items-center gap-2 text-sm font-medium text-gray-900"
83
+ >
61
84
  {getFieldIcon(field.name)}
62
85
  {label}
63
86
  </Label>
64
87
 
65
- {field.type === 'boolean' && (
88
+ {field.type === "boolean" && (
66
89
  <div className="flex items-center justify-between rounded-xl bg-gray-50 p-3">
67
90
  <span className="text-sm text-gray-500">
68
- {(formData[field.name] as boolean) ? t('enabled') : t('disabled')}
91
+ {(formData[field.name] as boolean)
92
+ ? t("enabled")
93
+ : t("disabled")}
69
94
  </span>
70
95
  <Switch
71
96
  id={field.name}
72
97
  checked={(formData[field.name] as boolean) || false}
73
- onCheckedChange={(checked) => updateField(field.name, checked)}
98
+ onCheckedChange={(checked) =>
99
+ updateField(field.name, checked)
100
+ }
74
101
  className="data-[state=checked]:bg-emerald-500"
75
102
  />
76
103
  </div>
77
104
  )}
78
105
 
79
- {(field.type === 'text' || field.type === 'email') && (
106
+ {(field.type === "text" || field.type === "email") && (
80
107
  <Input
81
108
  id={field.name}
82
109
  type={field.type}
83
- value={(formData[field.name] as string) || ''}
84
- onChange={(event) => updateField(field.name, event.target.value)}
110
+ value={(formData[field.name] as string) || ""}
111
+ onChange={(event) =>
112
+ updateField(field.name, event.target.value)
113
+ }
85
114
  placeholder={placeholder}
86
115
  className="rounded-xl"
87
116
  />
88
117
  )}
89
118
 
90
- {field.type === 'password' && (
119
+ {field.type === "password" && (
91
120
  <Input
92
121
  id={field.name}
93
122
  type="password"
94
- value={(formData[field.name] as string) || ''}
95
- onChange={(event) => updateField(field.name, event.target.value)}
96
- placeholder={placeholder ?? t('leaveBlankToKeepUnchanged')}
123
+ value={(formData[field.name] as string) || ""}
124
+ onChange={(event) =>
125
+ updateField(field.name, event.target.value)
126
+ }
127
+ placeholder={placeholder ?? t("leaveBlankToKeepUnchanged")}
97
128
  className="rounded-xl"
98
129
  />
99
130
  )}
100
131
 
101
- {field.type === 'number' && (
132
+ {field.type === "number" && (
102
133
  <Input
103
134
  id={field.name}
104
135
  type="number"
105
136
  value={(formData[field.name] as number) || 0}
106
- onChange={(event) => updateField(field.name, parseInt(event.target.value, 10) || 0)}
137
+ onChange={(event) =>
138
+ updateField(field.name, parseInt(event.target.value, 10) || 0)
139
+ }
107
140
  placeholder={placeholder}
108
141
  className="rounded-xl"
109
142
  />
110
143
  )}
111
144
 
112
- {field.type === 'tags' && (
145
+ {field.type === "tags" && (
113
146
  <TagInput
114
147
  value={(formData[field.name] as string[]) || []}
115
148
  onChange={(tags) => updateField(field.name, tags)}
116
149
  />
117
150
  )}
118
151
 
119
- {field.type === 'select' && (
152
+ {field.type === "select" && (
120
153
  <Select
121
- value={(formData[field.name] as string) || ''}
154
+ value={(formData[field.name] as string) || ""}
122
155
  onValueChange={(value) => updateField(field.name, value)}
123
156
  >
124
157
  <SelectTrigger className="rounded-xl">
@@ -134,14 +167,14 @@ export function ChannelFormFieldsSection({
134
167
  </Select>
135
168
  )}
136
169
 
137
- {field.type === 'json' && (
170
+ {field.type === "json" && (
138
171
  <textarea
139
172
  id={field.name}
140
- value={jsonDrafts[field.name] ?? '{}'}
173
+ value={jsonDrafts[field.name] ?? "{}"}
141
174
  onChange={(event) =>
142
175
  setJsonDrafts((prev) => ({
143
176
  ...prev,
144
- [field.name]: event.target.value
177
+ [field.name]: event.target.value,
145
178
  }))
146
179
  }
147
180
  className="min-h-[120px] w-full resize-none rounded-lg border border-gray-200 bg-white px-3 py-2 text-xs font-mono"
@@ -0,0 +1,109 @@
1
+ import type { ButtonHTMLAttributes, HTMLAttributes } from "react";
2
+ import type { LucideIcon } from "lucide-react";
3
+ import { cn } from "@/lib/utils";
4
+
5
+ const CARD_CLASS =
6
+ "min-w-0 overflow-hidden rounded-2xl border border-gray-200/70 bg-white shadow-card xl:h-[calc(100vh-180px)] xl:max-h-[860px]";
7
+ type DivProps = HTMLAttributes<HTMLDivElement>;
8
+ type SectionProps = HTMLAttributes<HTMLElement>;
9
+
10
+ function ConfigSplitPane({ className, ...props }: SectionProps) {
11
+ return <section className={cn(CARD_CLASS, "flex flex-col", className)} {...props} />;
12
+ }
13
+
14
+ export function ConfigSplitPage({ className, ...props }: DivProps) {
15
+ return (
16
+ <div
17
+ className={cn(
18
+ "grid min-h-0 grid-cols-1 gap-5 xl:flex-1 xl:grid-cols-[340px_minmax(0,1fr)]",
19
+ className,
20
+ )}
21
+ {...props}
22
+ />
23
+ );
24
+ }
25
+
26
+ export { ConfigSplitPane as ConfigSplitSidebar, ConfigSplitPane as ConfigSplitDetailPane };
27
+
28
+ export function ConfigSplitEmptyPane({ className, ...props }: SectionProps) {
29
+ return (
30
+ <section
31
+ className={cn(CARD_CLASS, "flex items-center justify-center px-6 py-12 text-center", className)}
32
+ {...props}
33
+ />
34
+ );
35
+ }
36
+
37
+ export function ConfigSplitPaneHeader({ className, ...props }: DivProps) {
38
+ return <div className={cn("shrink-0 border-b border-gray-100", className)} {...props} />;
39
+ }
40
+
41
+ export function ConfigSplitPaneBody({
42
+ className,
43
+ scrollOnDesktop = true,
44
+ ...props
45
+ }: DivProps & { scrollOnDesktop?: boolean }) {
46
+ return (
47
+ <div
48
+ className={cn(
49
+ "min-h-0 flex-1",
50
+ scrollOnDesktop && "overflow-visible xl:overflow-y-auto xl:overscroll-contain",
51
+ className,
52
+ )}
53
+ {...props}
54
+ />
55
+ );
56
+ }
57
+
58
+ export function ConfigSplitPaneFooter({ className, ...props }: DivProps) {
59
+ return <div className={cn("shrink-0 border-t border-gray-100", className)} {...props} />;
60
+ }
61
+
62
+ export function ConfigSelectionCard({
63
+ active = false,
64
+ className,
65
+ type = "button",
66
+ ...props
67
+ }: ButtonHTMLAttributes<HTMLButtonElement> & { active?: boolean }) {
68
+ return (
69
+ <button
70
+ type={type}
71
+ className={cn(
72
+ "w-full rounded-xl border p-2.5 text-left transition-all",
73
+ active
74
+ ? "border-primary/30 bg-primary-50/40 shadow-sm"
75
+ : "border-gray-200/70 bg-white hover:border-gray-300 hover:bg-gray-50/70",
76
+ className,
77
+ )}
78
+ {...props}
79
+ />
80
+ );
81
+ }
82
+
83
+ export function ConfigSplitEmptyState({
84
+ icon: Icon,
85
+ title,
86
+ description,
87
+ className,
88
+ ...props
89
+ }: HTMLAttributes<HTMLDivElement> & {
90
+ icon: LucideIcon;
91
+ title: string;
92
+ description?: string;
93
+ }) {
94
+ return (
95
+ <div
96
+ className={cn(
97
+ "flex min-h-[220px] flex-col items-center justify-center rounded-xl border border-dashed border-gray-200 bg-gray-50/70 px-4 py-10 text-center",
98
+ className,
99
+ )}
100
+ {...props}
101
+ >
102
+ <div className="mb-3 flex h-10 w-10 items-center justify-center rounded-lg bg-white">
103
+ <Icon className="h-5 w-5 text-gray-300" />
104
+ </div>
105
+ <p className="text-sm font-medium text-gray-700">{title}</p>
106
+ {description ? <p className="mt-2 text-xs text-gray-500">{description}</p> : null}
107
+ </div>
108
+ );
109
+ }
@@ -2,8 +2,8 @@ import { render, screen } from '@testing-library/react';
2
2
  import userEvent from '@testing-library/user-event';
3
3
  import { beforeEach, describe, expect, it, vi } from 'vitest';
4
4
  import { DesktopUpdateConfig } from '@/components/config/desktop-update-config';
5
- import { useDesktopUpdateStore } from '@/desktop/stores/desktop-update.store';
6
5
  import { setLanguage } from '@/lib/i18n';
6
+ import { useDesktopUpdateStore } from '@/platforms/desktop';
7
7
 
8
8
  const mocks = vi.hoisted(() => ({
9
9
  start: vi.fn(),
@@ -15,9 +15,15 @@ const mocks = vi.hoisted(() => ({
15
15
  updateChannel: vi.fn()
16
16
  }));
17
17
 
18
- vi.mock('@/desktop/managers/desktop-update.manager', () => ({
19
- desktopUpdateManager: mocks
20
- }));
18
+ vi.mock('@/platforms/desktop', async () => {
19
+ const actual = await vi.importActual<typeof import('@/platforms/desktop')>(
20
+ '@/platforms/desktop'
21
+ );
22
+ return {
23
+ ...actual,
24
+ desktopUpdateManager: mocks,
25
+ };
26
+ });
21
27
 
22
28
  describe('DesktopUpdateConfig', () => {
23
29
  beforeEach(() => {
@@ -5,11 +5,13 @@ import { Label } from '@/components/ui/label';
5
5
  import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
6
6
  import { Switch } from '@/components/ui/switch';
7
7
  import { PageHeader, PageLayout } from '@/components/layout/page-layout';
8
- import { desktopUpdateManager } from '@/desktop/managers/desktop-update.manager';
9
- import type { DesktopReleaseChannel } from '@/desktop/desktop-update.types';
10
- import { useDesktopUpdateStore } from '@/desktop/stores/desktop-update.store';
11
8
  import { formatDateTime, t } from '@/lib/i18n';
12
9
  import { cn } from '@/lib/utils';
10
+ import {
11
+ desktopUpdateManager,
12
+ type DesktopReleaseChannel,
13
+ useDesktopUpdateStore,
14
+ } from '@/platforms/desktop';
13
15
  import { Download, ExternalLink, RefreshCw, RotateCw } from 'lucide-react';
14
16
 
15
17
  function formatVersion(value: string | null): string {
@@ -1,6 +1,6 @@
1
- import { Label } from '@/components/ui/label';
2
- import { Switch } from '@/components/ui/switch';
3
- import { t } from '@/lib/i18n';
1
+ import { SettingRow } from "@/components/ui/setting-row";
2
+ import { Switch } from "@/components/ui/switch";
3
+ import { t } from "@/lib/i18n";
4
4
 
5
5
  type ProviderEnabledFieldProps = {
6
6
  enabled: boolean;
@@ -8,13 +8,20 @@ type ProviderEnabledFieldProps = {
8
8
  };
9
9
 
10
10
  export function ProviderEnabledField(props: ProviderEnabledFieldProps) {
11
+ const { enabled, onChange } = props;
12
+
11
13
  return (
12
- <div className="flex items-center justify-between rounded-xl border border-gray-200 bg-gray-50 px-4 py-3">
13
- <Label className="text-sm font-medium text-gray-900">{t('enabled')}</Label>
14
- <div className="flex items-center gap-3">
15
- <span className="text-xs text-gray-500">{props.enabled ? t('enabled') : t('disabled')}</span>
16
- <Switch checked={props.enabled} onCheckedChange={props.onChange} />
17
- </div>
18
- </div>
14
+ <SettingRow
15
+ tone="muted"
16
+ title={t("enabled")}
17
+ control={
18
+ <div className="flex items-center gap-3">
19
+ <span className="text-xs text-gray-500">
20
+ {enabled ? t("enabled") : t("disabled")}
21
+ </span>
22
+ <Switch checked={enabled} onCheckedChange={onChange} />
23
+ </div>
24
+ }
25
+ />
19
26
  );
20
27
  }