@nextclaw/ui 0.14.4 → 0.15.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 (194) hide show
  1. package/CHANGELOG.md +54 -0
  2. package/dist/assets/api-DLtsiBKm.js +13 -0
  3. package/dist/assets/appearance-settings-page-CtkeYXux.js +1 -0
  4. package/dist/assets/book-open-BQwWBDKb.js +1 -0
  5. package/dist/assets/channels-list-page-Cv8o9Hxg.js +8 -0
  6. package/dist/assets/chat-page-BtA7H1Ea.js +1 -0
  7. package/dist/assets/{config-split-page-CRhlZ_pH.js → config-split-page-EidRBpP2.js} +1 -1
  8. package/dist/assets/confirm-dialog-iIJFFp2T.js +5 -0
  9. package/dist/assets/desktop-update-config-C6n_TB1r.js +1 -0
  10. package/dist/assets/{dist-CikkhOlq.js → dist-CBxWOazz.js} +1 -1
  11. package/dist/assets/{dist-PVA_xL1D.js → dist-D2eZC3OR.js} +1 -1
  12. package/dist/assets/doc-browser-B76XOXmW.js +1 -0
  13. package/dist/assets/doc-browser-DBnj9UMB.js +1 -0
  14. package/dist/assets/{doc-browser-context-MTw4EvKd.js → doc-browser-context-Sv-044Cl.js} +1 -1
  15. package/dist/assets/{ellipsis-y6vDtq4M.js → ellipsis-2uzhypw5.js} +1 -1
  16. package/dist/assets/es2015-DhQo_oS6.js +41 -0
  17. package/dist/assets/{external-link-DVJIQf_G.js → external-link-CM5dnUI6.js} +1 -1
  18. package/dist/assets/host-capabilities-9bB_998Z.js +1 -0
  19. package/dist/assets/i18n-provider-di1waTGX.js +1 -0
  20. package/dist/assets/index-CBRP-7-a.css +1 -0
  21. package/dist/assets/index-Z1fYpbNd.js +104 -0
  22. package/dist/assets/loader-circle-BjmN5DeJ.js +1 -0
  23. package/dist/assets/mcp-marketplace-page-C_MVQUbT.js +9 -0
  24. package/dist/assets/mcp-marketplace-page-CoZIiD6G.js +1 -0
  25. package/dist/assets/model-config-page-Cdr9P6TQ.js +1 -0
  26. package/dist/assets/plus-3ztRZD_l.js +1 -0
  27. package/dist/assets/provider-scoped-model-input-g1WkGiJ8.js +1 -0
  28. package/dist/assets/providers-config-page-CWlO7J4A.js +1 -0
  29. package/dist/assets/{react-VNqT5Zp7.js → react-DjpzoZXB.js} +1 -1
  30. package/dist/assets/refresh-cw-DaTv3zxE.js +1 -0
  31. package/dist/assets/remote-CKWZfVVB.js +1 -0
  32. package/dist/assets/rotate-cw-CHcaRUDz.js +1 -0
  33. package/dist/assets/runtime-config-page-CfTqh1nv.js +1 -0
  34. package/dist/assets/{save-BOJ477H-.js → save-BZof1uie.js} +1 -1
  35. package/dist/assets/{search-BEti5beA.js → search-MxAarS_g.js} +1 -1
  36. package/dist/assets/search-config-page-Dm8daYny.js +1 -0
  37. package/dist/assets/{secrets-config-page--IzrIOid.js → secrets-config-page-D8AbcR05.js} +2 -2
  38. package/dist/assets/security-config-Ds0LsJmo.js +1 -0
  39. package/dist/assets/select-D7GcNP0z.js +1 -0
  40. package/dist/assets/setting-row-CuozlnJd.js +1 -0
  41. package/dist/assets/side-dock-BcLau5S_.js +3 -0
  42. package/dist/assets/skeleton-shfmXuYp.js +1 -0
  43. package/dist/assets/switch-C1BWMU5c.js +1 -0
  44. package/dist/assets/tag-chip-DCUrQUCP.js +1 -0
  45. package/dist/assets/tooltip-DfAHHA76.js +1 -0
  46. package/dist/assets/use-config-DAdbT5QJ.js +1 -0
  47. package/dist/assets/use-confirm-dialog-eWcJ6V-s.js +1 -0
  48. package/dist/assets/x-DqL1mYLC.js +1 -0
  49. package/dist/index.html +53 -17
  50. package/index.html +25 -2
  51. package/package.json +9 -9
  52. package/src/app/components/layout/__tests__/app-layout.test.tsx +27 -0
  53. package/src/app/components/layout/__tests__/settings-entry-page.test.tsx +9 -1
  54. package/src/app/components/layout/__tests__/sidebar.layout.test.tsx +6 -3
  55. package/src/app/components/layout/app-layout.tsx +10 -3
  56. package/src/app/components/layout/sidebar-items.tsx +8 -8
  57. package/src/app/components/layout/sidebar-rail.styles.ts +4 -2
  58. package/src/app/components/layout/sidebar.tsx +21 -28
  59. package/src/app/configs/app-navigation.config.ts +11 -5
  60. package/src/app/index.tsx +8 -0
  61. package/src/app/managers/__tests__/viewport-layout.manager.test.ts +59 -0
  62. package/src/app/managers/viewport-layout.manager.ts +39 -0
  63. package/src/app/presenters/app.presenter.ts +25 -2
  64. package/src/app/styles/design-system.css +91 -4
  65. package/src/features/chat/components/conversation/__tests__/chat-conversation-content.test.tsx +86 -0
  66. package/src/features/chat/components/conversation/chat-conversation-content.tsx +47 -17
  67. package/src/features/chat/components/layout/__tests__/chat-sidebar-read-state.test.tsx +7 -0
  68. package/src/features/chat/components/layout/chat-sidebar-desktop-layout.tsx +9 -5
  69. package/src/features/chat/components/layout/chat-sidebar-utility-menu.tsx +5 -5
  70. package/src/features/chat/features/conversation/components/__tests__/session-conversation-area.test.tsx +51 -2
  71. package/src/features/chat/features/conversation/components/__tests__/session-conversation-input.streaming.test.tsx +170 -1
  72. package/src/features/chat/features/conversation/components/session-conversation-area.tsx +23 -1
  73. package/src/features/chat/features/conversation/components/session-conversation-input.tsx +24 -7
  74. package/src/features/chat/features/conversation/components/session-queued-input-rows.tsx +96 -0
  75. package/src/features/chat/features/conversation/hooks/__tests__/use-session-conversation-controller.test.tsx +181 -0
  76. package/src/features/chat/features/conversation/hooks/use-session-conversation-controller.ts +351 -89
  77. package/src/features/chat/features/input/hooks/__tests__/use-chat-input-surface-state.test.tsx +29 -4
  78. package/src/features/chat/features/input/hooks/use-chat-input-surface-state.ts +28 -3
  79. package/src/features/chat/features/input/input-surface-plugins/__tests__/slash-command-plugin.test.ts +108 -8
  80. package/src/features/chat/features/input/input-surface-plugins/panel-app-reference-plugin.utils.ts +50 -14
  81. package/src/features/chat/features/input/input-surface-plugins/slash-command-plugin.utils.ts +77 -6
  82. package/src/features/chat/features/message/components/__tests__/chat-inline-panel-app-card.test.tsx +21 -12
  83. package/src/features/chat/features/message/components/__tests__/chat-message-list.container.test.tsx +166 -0
  84. package/src/features/chat/features/message/components/chat-inline-panel-app-card.tsx +19 -11
  85. package/src/features/chat/features/message/components/chat-message-list.container.tsx +38 -64
  86. package/src/features/chat/features/message/utils/__tests__/chat-message-process-summary.utils.test.ts +59 -0
  87. package/src/features/chat/features/message/utils/__tests__/chat-message-show-content-tool-card.utils.test.ts +56 -1
  88. package/src/features/chat/features/message/utils/chat-inline-panel-app-card.utils.ts +0 -2
  89. package/src/features/chat/features/message/utils/chat-message-process-summary.utils.ts +80 -0
  90. package/src/features/chat/features/message/utils/chat-message-show-content-tool-card.utils.ts +12 -3
  91. package/src/features/chat/features/message/utils/chat-message-texts.utils.ts +60 -0
  92. package/src/features/chat/features/message/utils/chat-message.utils.ts +3 -0
  93. package/src/features/chat/features/ncp/hooks/__tests__/use-ncp-agent-runtime.test.tsx +39 -0
  94. package/src/features/chat/features/session/components/chat-sidebar-project-groups.tsx +2 -2
  95. package/src/features/chat/features/session/components/chat-sidebar-session-item.tsx +1 -1
  96. package/src/features/chat/features/session/components/session-header/__tests__/chat-session-header-actions.test.tsx +3 -1
  97. package/src/features/chat/features/session/components/session-header/chat-session-metadata-dialog.tsx +1 -1
  98. package/src/features/chat/features/workspace/components/__tests__/chat-session-workspace-file-preview.test.tsx +270 -115
  99. package/src/features/chat/features/workspace/components/chat-session-workspace-file-breadcrumb-browser.tsx +121 -0
  100. package/src/features/chat/features/workspace/components/chat-session-workspace-file-breadcrumbs.tsx +75 -20
  101. package/src/features/chat/features/workspace/components/chat-session-workspace-file-preview.tsx +70 -5
  102. package/src/features/chat/features/workspace/utils/chat-workspace-file-tab-persistence.utils.ts +83 -0
  103. package/src/features/chat/managers/__tests__/chat-thread.manager.test.ts +79 -1
  104. package/src/features/chat/managers/__tests__/chat-ui.manager.test.ts +2 -0
  105. package/src/features/chat/managers/chat-thread.manager.ts +9 -3
  106. package/src/features/chat/managers/chat-ui.manager.ts +5 -1
  107. package/src/features/chat/presenters/chat.presenter.ts +2 -1
  108. package/src/features/chat/stores/__tests__/chat-thread.store.test.ts +4 -0
  109. package/src/features/chat/stores/chat-thread.store.ts +6 -64
  110. package/src/features/chat/types/chat-message.types.ts +4 -0
  111. package/src/features/marketplace/components/__tests__/marketplace-page-detail.test.tsx +33 -26
  112. package/src/features/marketplace/components/__tests__/marketplace-page.test.tsx +1 -0
  113. package/src/features/marketplace/components/curated-shelves/__tests__/marketplace-curated-scene-route.test.tsx +1 -0
  114. package/src/features/marketplace/components/detail-doc/__tests__/marketplace-detail-doc.test.tsx +115 -0
  115. package/src/features/marketplace/components/marketplace-detail-doc.tsx +441 -0
  116. package/src/features/marketplace/components/mcp/__tests__/mcp-marketplace-page.test.tsx +3 -3
  117. package/src/features/marketplace/components/mcp/mcp-marketplace-doc.ts +0 -60
  118. package/src/features/marketplace/components/mcp/mcp-marketplace-page.tsx +64 -29
  119. package/src/features/marketplace/hooks/use-marketplace-item-detail.ts +42 -31
  120. package/src/features/marketplace/index.ts +3 -0
  121. package/src/features/marketplace/stores/marketplace-detail-doc.store.ts +38 -0
  122. package/src/features/panel-apps/components/__tests__/panel-apps-list.test.tsx +32 -2
  123. package/src/features/pwa/managers/__tests__/pwa-shell-theme.manager.test.ts +11 -0
  124. package/src/features/pwa/managers/pwa-shell-theme.manager.ts +8 -3
  125. package/src/features/settings/pages/__tests__/appearance-settings-page.test.tsx +25 -0
  126. package/src/features/settings/pages/appearance-settings-page.tsx +40 -0
  127. package/src/features/side-dock/components/__tests__/side-dock.test.tsx +44 -2
  128. package/src/features/side-dock/components/side-dock.tsx +100 -13
  129. package/src/features/side-dock/configs/side-dock-built-in-items.config.ts +10 -0
  130. package/src/features/side-dock/managers/__tests__/side-dock.manager.test.ts +32 -2
  131. package/src/features/side-dock/managers/side-dock.manager.ts +17 -7
  132. package/src/features/side-dock/stores/side-dock.store.ts +9 -0
  133. package/src/features/side-dock/types/side-dock.types.ts +10 -3
  134. package/src/features/side-dock/utils/side-dock-item.utils.ts +15 -2
  135. package/src/index.css +501 -27
  136. package/src/platforms/desktop/components/desktop-window-chrome.tsx +1 -1
  137. package/src/shared/components/doc-browser/__tests__/doc-browser-context.test.tsx +30 -1
  138. package/src/shared/components/doc-browser/__tests__/doc-browser.test.tsx +206 -38
  139. package/src/shared/components/doc-browser/doc-browser-panel-parts.tsx +34 -11
  140. package/src/shared/components/doc-browser/doc-browser-renderer.types.ts +1 -1
  141. package/src/shared/components/doc-browser/doc-browser-tab-strip.tsx +23 -5
  142. package/src/shared/components/doc-browser/doc-browser.tsx +128 -75
  143. package/src/shared/components/doc-browser/managers/doc-browser.manager.ts +9 -1
  144. package/src/shared/components/doc-browser/utils/doc-browser-floating-panel.utils.ts +83 -0
  145. package/src/shared/components/ui/__tests__/dialog.test.tsx +27 -0
  146. package/src/shared/components/ui/actions/icon-action-button.tsx +14 -2
  147. package/src/shared/components/ui/dialog.tsx +2 -2
  148. package/src/shared/components/ui/popover.tsx +1 -1
  149. package/src/shared/components/ui/select.tsx +1 -1
  150. package/src/shared/components/ui/switch.tsx +4 -3
  151. package/src/shared/components/ui/tab-strip/__tests__/compact-tab-strip.test.tsx +54 -0
  152. package/src/shared/components/ui/tab-strip/compact-tab-strip.tsx +4 -5
  153. package/src/shared/components/ui/tooltip.tsx +1 -1
  154. package/src/shared/lib/api/utils/server-path.utils.ts +27 -1
  155. package/src/shared/lib/i18n/locales/en-US/chat.json +15 -6
  156. package/src/shared/lib/i18n/locales/en-US/core.json +2 -0
  157. package/src/shared/lib/i18n/locales/en-US/doc-browser.json +11 -0
  158. package/src/shared/lib/i18n/locales/en-US/marketplace.json +5 -0
  159. package/src/shared/lib/i18n/locales/zh-CN/chat.json +15 -6
  160. package/src/shared/lib/i18n/locales/zh-CN/core.json +2 -0
  161. package/src/shared/lib/i18n/locales/zh-CN/doc-browser.json +11 -0
  162. package/src/shared/lib/i18n/locales/zh-CN/marketplace.json +5 -0
  163. package/src/shared/lib/session-project/__tests__/workspace-file-breadcrumb.test.ts +14 -0
  164. package/src/shared/lib/session-project/index.ts +1 -1
  165. package/src/shared/lib/session-project/{workspace-file-breadcrumb.ts → workspace-file-breadcrumb.utils.ts} +62 -8
  166. package/src/shared/lib/theme/index.ts +51 -13
  167. package/dist/assets/api-BP_8IaxB.js +0 -13
  168. package/dist/assets/channels-list-page-DEh5Jjqx.js +0 -8
  169. package/dist/assets/chat-page-BRB0N5WW.js +0 -105
  170. package/dist/assets/confirm-dialog-DsivFJ4r.js +0 -5
  171. package/dist/assets/desktop-update-config-B0w9p2ep.js +0 -1
  172. package/dist/assets/doc-browser-BoNJ7-tE.js +0 -1
  173. package/dist/assets/doc-browser-CdJ_d4ik.js +0 -1
  174. package/dist/assets/index-BiE-K8-q.css +0 -1
  175. package/dist/assets/index-D9BAE_L_.js +0 -105
  176. package/dist/assets/mcp-marketplace-page-B1SZNY5m.js +0 -40
  177. package/dist/assets/mcp-marketplace-page-Cq_Tw42T.js +0 -1
  178. package/dist/assets/model-config-page-DotaTUHb.js +0 -1
  179. package/dist/assets/plus-BbZqquJe.js +0 -1
  180. package/dist/assets/provider-scoped-model-input-DsFRuWJH.js +0 -1
  181. package/dist/assets/providers-config-page-DW4tQSLN.js +0 -1
  182. package/dist/assets/remote-B8TiUfwW.js +0 -1
  183. package/dist/assets/rotate-cw-DjHDTkCv.js +0 -1
  184. package/dist/assets/runtime-config-page-S6ErRHR8.js +0 -1
  185. package/dist/assets/search-config-page-DGj5am03.js +0 -1
  186. package/dist/assets/select-hoj8dK4K.js +0 -41
  187. package/dist/assets/tag-chip-T8-h-die.js +0 -1
  188. package/dist/assets/use-config-CjRV2hwL.js +0 -1
  189. package/dist/assets/x-D9PXs_xP.js +0 -1
  190. package/src/features/marketplace/components/detail-doc/__tests__/marketplace-detail-doc.test.ts +0 -40
  191. package/src/features/marketplace/components/detail-doc/marketplace-detail-doc-renderer.ts +0 -201
  192. package/src/features/marketplace/components/marketplace-detail-doc.ts +0 -142
  193. /package/dist/assets/{config-hints-BkYBc7z0.js → config-hints-fGnUjDe9.js} +0 -0
  194. /package/dist/assets/{middleware-CkFT3R4v.js → middleware-BILWbJE4.js} +0 -0
package/index.html CHANGED
@@ -12,7 +12,7 @@
12
12
  <meta name="mobile-web-app-capable" content="yes" />
13
13
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
14
14
  <meta name="theme-color" content="#FAF9F7" />
15
- <meta name="color-scheme" content="light" />
15
+ <meta name="color-scheme" content="light dark" />
16
16
  <style>
17
17
  html,
18
18
  body,
@@ -50,6 +50,12 @@
50
50
  background: #f8f8f7;
51
51
  }
52
52
 
53
+ html[data-theme="night"],
54
+ html[data-theme="night"] body,
55
+ html[data-theme="night"] #root {
56
+ background: #101318;
57
+ }
58
+
53
59
  html[data-theme="probe"],
54
60
  html[data-theme="probe"] body,
55
61
  html[data-theme="probe"] #root {
@@ -67,8 +73,19 @@
67
73
  cool: '#F8FAFC',
68
74
  dawn: '#FAF7F4',
69
75
  graphite: '#F8F8F7',
76
+ night: '#101318',
70
77
  probe: '#FEFCF6'
71
78
  };
79
+ const themeAppearances = {
80
+ natural: 'light',
81
+ minimal: 'light',
82
+ warm: 'light',
83
+ cool: 'light',
84
+ dawn: 'light',
85
+ graphite: 'light',
86
+ night: 'dark',
87
+ probe: 'light'
88
+ };
72
89
  const legacyThemeAliases = {
73
90
  leaf: 'warm'
74
91
  };
@@ -82,13 +99,19 @@
82
99
  window.localStorage.setItem(storageKey, nextTheme);
83
100
  }
84
101
  const themeColor = themeColors[nextTheme];
102
+ const themeAppearance = themeAppearances[nextTheme];
85
103
  document.documentElement.setAttribute('data-theme', nextTheme);
104
+ document.documentElement.setAttribute('data-theme-appearance', themeAppearance);
86
105
  document.documentElement.style.backgroundColor = themeColor;
87
- document.documentElement.style.colorScheme = 'light';
106
+ document.documentElement.style.colorScheme = themeAppearance;
88
107
  const themeMeta = document.querySelector('meta[name="theme-color"]');
89
108
  if (themeMeta) {
90
109
  themeMeta.setAttribute('content', themeColor);
91
110
  }
111
+ const colorSchemeMeta = document.querySelector('meta[name="color-scheme"]');
112
+ if (colorSchemeMeta) {
113
+ colorSchemeMeta.setAttribute('content', themeAppearance);
114
+ }
92
115
  })();
93
116
  </script>
94
117
  <script>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nextclaw/ui",
3
- "version": "0.14.4",
3
+ "version": "0.15.0",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -28,14 +28,14 @@
28
28
  "tailwind-merge": "^2.5.4",
29
29
  "zod": "^3.23.8",
30
30
  "zustand": "^5.0.2",
31
- "@nextclaw/agent-chat-ui": "0.5.4",
32
- "@nextclaw/agent-chat": "0.2.16",
33
- "@nextclaw/ncp": "0.6.6",
34
- "@nextclaw/client-sdk": "0.4.7",
35
- "@nextclaw/ncp-http-agent-client": "0.3.48",
36
- "@nextclaw/ncp-toolkit": "0.5.41",
37
- "@nextclaw/shared": "0.3.3",
38
- "@nextclaw/ncp-react": "0.4.56"
31
+ "@nextclaw/agent-chat-ui": "0.6.0",
32
+ "@nextclaw/agent-chat": "0.3.0",
33
+ "@nextclaw/client-sdk": "0.5.0",
34
+ "@nextclaw/ncp": "0.7.0",
35
+ "@nextclaw/ncp-toolkit": "0.6.0",
36
+ "@nextclaw/ncp-react": "0.5.0",
37
+ "@nextclaw/shared": "0.4.0",
38
+ "@nextclaw/ncp-http-agent-client": "0.4.0"
39
39
  },
40
40
  "devDependencies": {
41
41
  "@testing-library/react": "^16.3.0",
@@ -6,6 +6,7 @@ import { beforeEach, describe, expect, it, vi } from "vitest";
6
6
  import { AppLayout } from "@/app/components/layout/app-layout";
7
7
  import { I18nProvider } from "@/app/components/i18n-provider";
8
8
  import { viewportLayoutManager } from "@/app/managers/viewport-layout.manager";
9
+ import { useSideDockStore } from "@/features/side-dock";
9
10
 
10
11
  const { useViewportLayoutMock } = vi.hoisted(() => ({
11
12
  useViewportLayoutMock: vi.fn(() => ({
@@ -53,6 +54,8 @@ describe("AppLayout", () => {
53
54
  isDesktop: true,
54
55
  });
55
56
  window.nextclawDesktop = undefined;
57
+ useSideDockStore.getState().setVisible(true);
58
+ useSideDockStore.getState().setPinnedItems([]);
56
59
  });
57
60
 
58
61
  it("treats /agents as a main workspace route instead of the settings shell", () => {
@@ -79,6 +82,30 @@ describe("AppLayout", () => {
79
82
  expect(screen.getByTestId("side-dock")).toBeTruthy();
80
83
  });
81
84
 
85
+ it("hides the side dock when the persisted visibility preference is off", () => {
86
+ useSideDockStore.getState().setVisible(false);
87
+
88
+ render(
89
+ <I18nProvider>
90
+ <MemoryRouter initialEntries={["/chat"]}>
91
+ <Routes>
92
+ <Route
93
+ path="*"
94
+ element={
95
+ <AppLayout>
96
+ <div data-testid="chat-content">Chat Content</div>
97
+ </AppLayout>
98
+ }
99
+ />
100
+ </Routes>
101
+ </MemoryRouter>
102
+ </I18nProvider>,
103
+ );
104
+
105
+ expect(screen.getByTestId("chat-content")).toBeTruthy();
106
+ expect(screen.queryByTestId("side-dock")).toBeNull();
107
+ });
108
+
82
109
  it("keeps settings routes on the shared shell without channel-specific scroll locking", () => {
83
110
  const { container } = render(
84
111
  <I18nProvider>
@@ -23,7 +23,7 @@ vi.mock("@/platforms/mobile", async () => ({
23
23
  }));
24
24
 
25
25
  describe("SettingsEntryPage", () => {
26
- it("redirects desktop users to the model settings page", () => {
26
+ it("redirects desktop users to the primary model settings page", () => {
27
27
  render(
28
28
  <I18nProvider>
29
29
  <MemoryRouter initialEntries={["/settings"]}>
@@ -61,6 +61,14 @@ describe("SettingsEntryPage", () => {
61
61
  expect(screen.getByTestId("mobile-settings-shell")).toBeTruthy();
62
62
  const links = screen.getAllByRole("link");
63
63
  expect(links[0]?.textContent?.trim()).toBe("Language");
64
+ expect(links.slice(1, 7).map((link) => link.textContent?.trim())).toEqual([
65
+ "Model",
66
+ "Providers",
67
+ "Channels",
68
+ "Search Channels",
69
+ "Appearance",
70
+ "Security",
71
+ ]);
64
72
  expect(links.some((link) => link.getAttribute("href") === "/cron")).toBe(
65
73
  false,
66
74
  );
@@ -81,6 +81,7 @@ describe("Sidebar", () => {
81
81
  expect(nav?.className).toContain("flex-1");
82
82
  expect(nav?.className).toContain("min-h-0");
83
83
  expect(nav?.className).toContain("overflow-y-auto");
84
+ expect(nav?.className).toContain("[mask-image:linear-gradient");
84
85
  expect(
85
86
  screen.getByRole("link", { current: "page" }).className,
86
87
  ).not.toContain("font-semibold");
@@ -101,7 +102,7 @@ describe("Sidebar", () => {
101
102
  expect(backLink).toBeTruthy();
102
103
  expect(header.className).not.toContain("bg-white");
103
104
  expect(header.className).not.toContain("rounded-2xl");
104
- expect(backLink.className).toContain("hover:bg-accent");
105
+ expect(backLink.className).toContain("hover:bg-gray-200/60");
105
106
  });
106
107
 
107
108
  it("keeps the settings navigation in the expected product order", () => {
@@ -125,10 +126,11 @@ describe("Sidebar", () => {
125
126
  "Providers",
126
127
  "Channels",
127
128
  "Search Channels",
129
+ "Appearance",
130
+ "Security",
128
131
  "Routing & Runtime",
129
132
  "Updates",
130
133
  "Remote Access",
131
- "Security",
132
134
  "Secrets",
133
135
  "MCP",
134
136
  ]);
@@ -149,7 +151,8 @@ describe("Sidebar", () => {
149
151
  expect(screen.getByText("user@example.com")).toBeTruthy();
150
152
  expect(accountEntry.className).toContain("py-2");
151
153
  expect(accountEntry.className).toContain("text-muted-foreground");
152
- expect(accountEntry.className).toContain("hover:bg-accent");
154
+ expect(accountEntry.className).toContain("hover:bg-gray-200/60");
155
+ expect(accountEntry.parentElement?.className).not.toContain("border-t");
153
156
  expect(accountStatus.className).toContain("text-[11px]");
154
157
  });
155
158
 
@@ -10,6 +10,7 @@ import { useViewportLayout } from "@/app/hooks/use-viewport-layout";
10
10
  import { DesktopAppShell, getDesktopHostPlatform } from "@/platforms/desktop";
11
11
  import { MobileAppShell } from "@/platforms/mobile";
12
12
  import { PANEL_APPS_DOC_BROWSER_RENDERERS } from "@/features/panel-apps";
13
+ import { MARKETPLACE_DETAIL_DOC_BROWSER_RENDERERS } from "@/features/marketplace";
13
14
  import {
14
15
  SideDock,
15
16
  type SideDockManager,
@@ -23,6 +24,11 @@ interface AppLayoutProps {
23
24
  children: React.ReactNode;
24
25
  }
25
26
 
27
+ const DOC_BROWSER_RENDERERS = {
28
+ ...PANEL_APPS_DOC_BROWSER_RENDERERS,
29
+ ...MARKETPLACE_DETAIL_DOC_BROWSER_RENDERERS,
30
+ };
31
+
26
32
  function AppLayoutInner({
27
33
  children,
28
34
  sideDockManager,
@@ -33,6 +39,7 @@ function AppLayoutInner({
33
39
  const { language } = useI18n();
34
40
  const { isMobile } = useViewportLayout();
35
41
  const desktopHostPlatform = getDesktopHostPlatform();
42
+ const isSideDockVisible = useSideDockStore((state) => state.isVisible);
36
43
  useSideDockStore((state) => state.pinnedItems);
37
44
  const docBrowserDockControls: DocBrowserDockControls = {
38
45
  getDockState: sideDockManager.getDockState,
@@ -50,7 +57,7 @@ function AppLayoutInner({
50
57
  pathname={pathname}
51
58
  isDocBrowserOpen={isOpen}
52
59
  docBrowserDockControls={docBrowserDockControls}
53
- docBrowserRenderers={PANEL_APPS_DOC_BROWSER_RENDERERS}
60
+ docBrowserRenderers={DOC_BROWSER_RENDERERS}
54
61
  topbarLeadingInset={
55
62
  desktopHostPlatform === "darwin" ? "4.75rem" : undefined
56
63
  }
@@ -67,8 +74,8 @@ function AppLayoutInner({
67
74
  isDocBrowserOpen={isOpen}
68
75
  docBrowserMode={mode}
69
76
  docBrowserDockControls={docBrowserDockControls}
70
- docBrowserRenderers={PANEL_APPS_DOC_BROWSER_RENDERERS}
71
- sideDock={<SideDock manager={sideDockManager} />}
77
+ docBrowserRenderers={DOC_BROWSER_RENDERERS}
78
+ sideDock={isSideDockVisible ? <SideDock manager={sideDockManager} /> : null}
72
79
  >
73
80
  {children}
74
81
  </DesktopAppShell>
@@ -111,10 +111,10 @@ export function SidebarNavLinkItem({
111
111
  isActive
112
112
  ? collapsed
113
113
  ? SIDEBAR_RAIL_ACTIVE_SURFACE_CLASS
114
- : "bg-accent text-accent-foreground shadow-sm"
114
+ : "bg-gray-200/80 text-gray-900 shadow-sm"
115
115
  : collapsed
116
116
  ? SIDEBAR_RAIL_SURFACE_CLASS
117
- : "text-muted-foreground hover:bg-accent hover:text-accent-foreground",
117
+ : "text-muted-foreground hover:bg-gray-200/60 hover:text-gray-900",
118
118
  className,
119
119
  )}
120
120
  >
@@ -123,8 +123,8 @@ export function SidebarNavLinkItem({
123
123
  collapsed ? SIDEBAR_RAIL_ICON_CLASS : tone.icon,
124
124
  "shrink-0 transition-colors",
125
125
  isActive
126
- ? "text-accent-foreground"
127
- : "text-muted-foreground/75 group-hover:text-accent-foreground",
126
+ ? "text-gray-700"
127
+ : "text-muted-foreground/75 group-hover:text-gray-700",
128
128
  )}
129
129
  />
130
130
  <span className={collapsed ? "sr-only" : "min-w-0 flex-1 text-left"}>
@@ -174,7 +174,7 @@ export function SidebarActionItem({
174
174
  onClick={onClick}
175
175
  aria-label={label}
176
176
  className={cn(
177
- "group flex w-full items-center rounded-xl font-medium text-muted-foreground transition-all duration-base hover:bg-accent hover:text-accent-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/35",
177
+ "group flex w-full items-center rounded-xl font-medium text-muted-foreground transition-colors duration-base hover:bg-gray-200/60 hover:text-gray-900 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/35",
178
178
  collapsed
179
179
  ? cn(
180
180
  SIDEBAR_RAIL_CONTROL_CLASS,
@@ -189,7 +189,7 @@ export function SidebarActionItem({
189
189
  <Icon
190
190
  className={cn(
191
191
  collapsed ? SIDEBAR_RAIL_ICON_CLASS : tone.icon,
192
- "shrink-0 text-muted-foreground/75 transition-colors group-hover:text-accent-foreground",
192
+ "shrink-0 text-muted-foreground/75 transition-colors group-hover:text-gray-700",
193
193
  )}
194
194
  />
195
195
  <span
@@ -248,7 +248,7 @@ export function SidebarSelectItem({
248
248
  <SelectTrigger
249
249
  aria-label={label}
250
250
  className={cn(
251
- "group h-auto w-full rounded-xl border-0 bg-transparent font-medium text-muted-foreground shadow-none hover:bg-accent hover:text-accent-foreground focus:ring-0 focus-visible:ring-2 focus-visible:ring-primary/35",
251
+ "group h-auto w-full rounded-xl border-0 bg-transparent font-medium text-muted-foreground shadow-none hover:bg-gray-200/60 hover:text-gray-900 focus:ring-0 focus-visible:ring-2 focus-visible:ring-primary/35",
252
252
  collapsed
253
253
  ? cn(
254
254
  SIDEBAR_RAIL_CONTROL_CLASS,
@@ -267,7 +267,7 @@ export function SidebarSelectItem({
267
267
  <Icon
268
268
  className={cn(
269
269
  collapsed ? SIDEBAR_RAIL_ICON_CLASS : tone.icon,
270
- "shrink-0 text-muted-foreground/75 transition-colors group-hover:text-accent-foreground",
270
+ "shrink-0 text-muted-foreground/75 transition-colors group-hover:text-gray-700",
271
271
  )}
272
272
  />
273
273
  <span className={collapsed ? "sr-only" : "text-left"}>{label}</span>
@@ -5,11 +5,13 @@ export const SIDEBAR_RAIL_CONTROL_CLASS = "h-9 w-9 rounded-xl";
5
5
  export const SIDEBAR_RAIL_ICON_CLASS = "h-4 w-4";
6
6
  export const SIDEBAR_RAIL_STACK_CLASS = "space-y-1";
7
7
  export const SIDEBAR_RAIL_ITEM_GAP_CLASS = "gap-1";
8
+ export const SIDEBAR_SCROLL_EDGE_FADE_CLASS =
9
+ "[mask-image:linear-gradient(to_bottom,black_calc(100%_-_28px),transparent)] [-webkit-mask-image:linear-gradient(to_bottom,black_calc(100%_-_28px),transparent)]";
8
10
 
9
11
  export const SIDEBAR_RAIL_SURFACE_CLASS =
10
- "text-muted-foreground transition-colors duration-base hover:bg-accent hover:text-accent-foreground active:bg-accent focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/35";
12
+ "text-muted-foreground transition-colors duration-base hover:bg-gray-200/60 hover:text-gray-900 active:bg-gray-200/80 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/35";
11
13
 
12
- export const SIDEBAR_RAIL_ACTIVE_SURFACE_CLASS = "bg-accent text-accent-foreground";
14
+ export const SIDEBAR_RAIL_ACTIVE_SURFACE_CLASS = "bg-gray-200/80 text-gray-900";
13
15
 
14
16
  export const SIDEBAR_RAIL_PRIMARY_SURFACE_CLASS =
15
17
  "bg-primary/10 text-primary transition-colors duration-base hover:bg-primary/15 hover:text-primary-700 active:bg-primary/20 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/35";
@@ -41,6 +41,7 @@ import {
41
41
  SIDEBAR_RAIL_STACK_CLASS,
42
42
  SIDEBAR_RAIL_SURFACE_CLASS,
43
43
  SIDEBAR_RAIL_WIDTH_CLASS,
44
+ SIDEBAR_SCROLL_EDGE_FADE_CLASS,
44
45
  } from "@/app/components/layout/sidebar-rail.styles";
45
46
 
46
47
  type SidebarMode = "main" | "settings";
@@ -105,7 +106,7 @@ function SettingsSidebarHeader({
105
106
  to="/chat"
106
107
  aria-label={t("backToMain")}
107
108
  className={cn(
108
- "group inline-flex min-w-0 items-center rounded-lg text-[12px] font-medium text-muted-foreground transition-colors hover:bg-accent hover:text-accent-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/35",
109
+ "group inline-flex min-w-0 items-center rounded-lg text-[12px] font-medium text-muted-foreground transition-colors hover:bg-gray-200/60 hover:text-gray-900 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/35",
109
110
  isCollapsed
110
111
  ? cn(
111
112
  SIDEBAR_RAIL_CONTROL_CLASS,
@@ -118,7 +119,7 @@ function SettingsSidebarHeader({
118
119
  <ArrowLeft
119
120
  className={cn(
120
121
  isCollapsed ? SIDEBAR_RAIL_ICON_CLASS : "h-3.5 w-3.5",
121
- "shrink-0 text-muted-foreground/75 group-hover:text-accent-foreground",
122
+ "shrink-0 text-muted-foreground/75 group-hover:text-gray-700",
122
123
  )}
123
124
  />
124
125
  <span className={isCollapsed ? "sr-only" : "truncate"}>
@@ -208,6 +209,7 @@ function SidebarNavigation({
208
209
  className={cn(
209
210
  "custom-scrollbar min-h-0 flex-1 overflow-y-auto",
210
211
  isCollapsed ? "pr-0" : "pr-1",
212
+ SIDEBAR_SCROLL_EDGE_FADE_CLASS,
211
213
  )}
212
214
  >
213
215
  <ul
@@ -219,22 +221,20 @@ function SidebarNavigation({
219
221
  : "space-y-1 pb-4",
220
222
  )}
221
223
  >
222
- {items.map((item) => {
223
- return (
224
- <li
225
- key={item.target}
226
- className={isCollapsed ? "flex justify-center" : undefined}
227
- >
228
- <SidebarNavLinkItem
229
- to={item.target}
230
- label={item.label}
231
- icon={item.icon}
232
- density={density}
233
- collapsed={isCollapsed}
234
- />
235
- </li>
236
- );
237
- })}
224
+ {items.map((item) => (
225
+ <li
226
+ key={item.target}
227
+ className={isCollapsed ? "flex justify-center" : undefined}
228
+ >
229
+ <SidebarNavLinkItem
230
+ to={item.target}
231
+ label={item.label}
232
+ icon={item.icon}
233
+ density={density}
234
+ collapsed={isCollapsed}
235
+ />
236
+ </li>
237
+ ))}
238
238
  </ul>
239
239
  </nav>
240
240
  );
@@ -264,13 +264,9 @@ export function Sidebar({ mode }: SidebarProps) {
264
264
  const accountConnected = Boolean(remoteStatus.data?.account.loggedIn);
265
265
 
266
266
  const handleThemeSwitch = (nextTheme: UiTheme) => {
267
- if (theme === nextTheme) {
268
- return;
269
- }
270
- setTheme(nextTheme);
267
+ if (theme !== nextTheme) setTheme(nextTheme);
271
268
  };
272
269
 
273
- // Core navigation items - primary features
274
270
  const mainNavItems: SidebarNavItem[] = [
275
271
  {
276
272
  target: "/chat",
@@ -294,8 +290,7 @@ export function Sidebar({ mode }: SidebarProps) {
294
290
  },
295
291
  ];
296
292
 
297
- const settingsNavItems = getSettingsNavItems(t);
298
- const navItems = isSettingsMode ? settingsNavItems : mainNavItems;
293
+ const navItems = isSettingsMode ? getSettingsNavItems(t) : mainNavItems;
299
294
  const sidebarDensity = isSettingsMode ? "compact" : "default";
300
295
 
301
296
  return (
@@ -315,7 +310,6 @@ export function Sidebar({ mode }: SidebarProps) {
315
310
  />
316
311
 
317
312
  <div className="flex min-h-0 flex-1 flex-col">
318
- {/* Navigation */}
319
313
  <SidebarNavigation
320
314
  isSettingsMode={isSettingsMode}
321
315
  isCollapsed={isCollapsed}
@@ -323,10 +317,9 @@ export function Sidebar({ mode }: SidebarProps) {
323
317
  density={sidebarDensity}
324
318
  />
325
319
 
326
- {/* Footer actions stay reachable while the nav scrolls independently. */}
327
320
  <div
328
321
  className={cn(
329
- "shrink-0 border-t border-border/70 bg-secondary",
322
+ "shrink-0 bg-secondary",
330
323
  isCollapsed
331
324
  ? "mt-2 pt-2"
332
325
  : isSettingsMode
@@ -8,6 +8,7 @@ import {
8
8
  Languages,
9
9
  MessageCircle,
10
10
  MessageSquare,
11
+ Palette,
11
12
  Search,
12
13
  Settings,
13
14
  Shield,
@@ -103,6 +104,16 @@ export function getSettingsNavItems(
103
104
  label: translate("searchChannels"),
104
105
  icon: Search,
105
106
  },
107
+ {
108
+ target: "/appearance",
109
+ label: translate("appearance"),
110
+ icon: Palette,
111
+ },
112
+ {
113
+ target: "/security",
114
+ label: translate("security"),
115
+ icon: Shield,
116
+ },
106
117
  {
107
118
  target: "/runtime",
108
119
  label: translate("runtime"),
@@ -118,11 +129,6 @@ export function getSettingsNavItems(
118
129
  label: translate("remote"),
119
130
  icon: Wifi,
120
131
  },
121
- {
122
- target: "/security",
123
- label: translate("security"),
124
- icon: Shield,
125
- },
126
132
  {
127
133
  target: "/secrets",
128
134
  label: translate("secrets"),
package/src/app/index.tsx CHANGED
@@ -26,6 +26,10 @@ const ModelConfigPage = lazy(async () => ({
26
26
  default: (await import("@/features/settings/pages/model-config-page"))
27
27
  .ModelConfigPage,
28
28
  }));
29
+ const AppearanceSettingsPage = lazy(async () => ({
30
+ default: (await import("@/features/settings/pages/appearance-settings-page"))
31
+ .AppearanceSettingsPage,
32
+ }));
29
33
  const ChatPage = lazy(async () => ({
30
34
  default: (await import("@/components/chat/chat-page")).ChatPage,
31
35
  }));
@@ -114,6 +118,10 @@ const protectedRouteDefinitions: ProtectedRouteDefinition[] = [
114
118
  path: "/cron",
115
119
  element: createLazyElement(<ChatPage view="cron" />),
116
120
  },
121
+ {
122
+ path: "/appearance",
123
+ element: createLazyElement(<AppearanceSettingsPage />),
124
+ },
117
125
  {
118
126
  path: "/model",
119
127
  element: createLazyElement(<ModelConfigPage />),
@@ -0,0 +1,59 @@
1
+ import { beforeEach, describe, expect, it } from "vitest";
2
+ import {
3
+ DENSE_RIGHT_PANELS_AUTO_COLLAPSE_MAX_WIDTH,
4
+ viewportLayoutManager,
5
+ } from "@/app/managers/viewport-layout.manager";
6
+ import { useViewportLayoutStore } from "@/app/stores/viewport-layout.store";
7
+
8
+ function setViewportWidth(width: number): void {
9
+ Object.defineProperty(window, "innerWidth", {
10
+ configurable: true,
11
+ value: width,
12
+ writable: true,
13
+ });
14
+ viewportLayoutManager.resetForTests();
15
+ }
16
+
17
+ describe("ViewportLayoutManager dense right panels", () => {
18
+ beforeEach(() => {
19
+ window.localStorage.clear();
20
+ setViewportWidth(DENSE_RIGHT_PANELS_AUTO_COLLAPSE_MAX_WIDTH);
21
+ });
22
+
23
+ it("collapses the left sidebar once when two docked right panels crowd a desktop viewport", () => {
24
+ viewportLayoutManager.collapseSidebarForDenseRightPanels({
25
+ isDocBrowserDocked: true,
26
+ isDocBrowserOpen: true,
27
+ isWorkspacePanelOpen: true,
28
+ });
29
+
30
+ expect(useViewportLayoutStore.getState().isSidebarCollapsed).toBe(true);
31
+ });
32
+
33
+ it("keeps the left sidebar expanded when only one right panel is open", () => {
34
+ viewportLayoutManager.collapseSidebarForDenseRightPanels({
35
+ isDocBrowserDocked: true,
36
+ isDocBrowserOpen: true,
37
+ isWorkspacePanelOpen: false,
38
+ });
39
+
40
+ expect(useViewportLayoutStore.getState().isSidebarCollapsed).toBe(false);
41
+ });
42
+
43
+ it("does not collapse the left sidebar for floating DocBrowser or wide desktop layouts", () => {
44
+ viewportLayoutManager.collapseSidebarForDenseRightPanels({
45
+ isDocBrowserDocked: false,
46
+ isDocBrowserOpen: true,
47
+ isWorkspacePanelOpen: true,
48
+ });
49
+ expect(useViewportLayoutStore.getState().isSidebarCollapsed).toBe(false);
50
+
51
+ setViewportWidth(DENSE_RIGHT_PANELS_AUTO_COLLAPSE_MAX_WIDTH + 1);
52
+ viewportLayoutManager.collapseSidebarForDenseRightPanels({
53
+ isDocBrowserDocked: true,
54
+ isDocBrowserOpen: true,
55
+ isWorkspacePanelOpen: true,
56
+ });
57
+ expect(useViewportLayoutStore.getState().isSidebarCollapsed).toBe(false);
58
+ });
59
+ });
@@ -5,6 +5,14 @@ import {
5
5
  type ViewportLayoutSnapshot,
6
6
  } from "@/app/stores/viewport-layout.store";
7
7
 
8
+ export const DENSE_RIGHT_PANELS_AUTO_COLLAPSE_MAX_WIDTH = 1800;
9
+
10
+ type DenseRightPanelsLayout = {
11
+ isDocBrowserDocked: boolean;
12
+ isDocBrowserOpen: boolean;
13
+ isWorkspacePanelOpen: boolean;
14
+ };
15
+
8
16
  export class ViewportLayoutManager {
9
17
  private consumerCount = 0;
10
18
 
@@ -51,6 +59,37 @@ export class ViewportLayoutManager {
51
59
  useViewportLayoutStore.setState({ isSidebarCollapsed });
52
60
  };
53
61
 
62
+ collapseSidebarForDenseRightPanels = (layout: DenseRightPanelsLayout) => {
63
+ if (
64
+ !layout.isDocBrowserOpen ||
65
+ !layout.isDocBrowserDocked ||
66
+ !layout.isWorkspacePanelOpen
67
+ ) {
68
+ return;
69
+ }
70
+
71
+ const snapshot = this.getSnapshot();
72
+ if (snapshot.mode !== "desktop" || snapshot.isSidebarCollapsed) {
73
+ return;
74
+ }
75
+
76
+ const width =
77
+ typeof snapshot.width === "number"
78
+ ? snapshot.width
79
+ : typeof window === "undefined"
80
+ ? null
81
+ : window.innerWidth;
82
+ if (
83
+ typeof width !== "number" ||
84
+ !Number.isFinite(width) ||
85
+ width > DENSE_RIGHT_PANELS_AUTO_COLLAPSE_MAX_WIDTH
86
+ ) {
87
+ return;
88
+ }
89
+
90
+ this.setSidebarCollapsed(true);
91
+ };
92
+
54
93
  toggleSidebarCollapsed = () => {
55
94
  useViewportLayoutStore.setState((state) => ({
56
95
  isSidebarCollapsed: !state.isSidebarCollapsed,
@@ -1,16 +1,39 @@
1
1
  import { AccountManager } from '@/features/account';
2
- import { ChatDraftIntentManager } from '@/features/chat';
2
+ import { viewportLayoutManager } from '@/app/managers/viewport-layout.manager';
3
+ import {
4
+ ChatDraftIntentManager,
5
+ useChatThreadStore,
6
+ } from '@/features/chat';
3
7
  import { PanelAppBridgeManager } from '@/features/panel-apps';
4
8
  import { RightPanelResourceRouteResolver } from '@/features/right-panel-resources';
5
9
  import { RemoteAccessManager } from '@/features/remote';
6
10
  import { ServiceActionAuthorizationManager } from '@/features/service-apps';
7
11
  import { SideDockManager } from '@/features/side-dock';
8
12
  import { DocBrowserManager } from '@/shared/components/doc-browser/managers/doc-browser.manager';
13
+ import { useDocBrowserStore } from '@/shared/components/doc-browser/stores/doc-browser.store';
14
+
15
+ type ChatThreadSnapshot = ReturnType<typeof useChatThreadStore.getState>['snapshot'];
16
+
17
+ function isChatWorkspacePanelOpen(snapshot: ChatThreadSnapshot): boolean {
18
+ return snapshot.activeWorkspacePanelKind != null;
19
+ }
9
20
 
10
21
  export class AppPresenter {
11
22
  accountManager = new AccountManager();
12
23
  rightPanelResourceRouteResolver = new RightPanelResourceRouteResolver();
13
- docBrowserManager = new DocBrowserManager(this.rightPanelResourceRouteResolver);
24
+ notifyRightPanelOpened = () => {
25
+ const docBrowser = useDocBrowserStore.getState().snapshot;
26
+ const chatThread = useChatThreadStore.getState().snapshot;
27
+ viewportLayoutManager.collapseSidebarForDenseRightPanels({
28
+ isDocBrowserDocked: docBrowser.mode === 'docked',
29
+ isDocBrowserOpen: docBrowser.isOpen,
30
+ isWorkspacePanelOpen: isChatWorkspacePanelOpen(chatThread),
31
+ });
32
+ };
33
+ docBrowserManager = new DocBrowserManager(
34
+ this.rightPanelResourceRouteResolver,
35
+ this.notifyRightPanelOpened,
36
+ );
14
37
  sideDockManager = new SideDockManager(this.docBrowserManager);
15
38
  chatDraftIntentManager = new ChatDraftIntentManager();
16
39
  serviceActionAuthorizationManager = new ServiceActionAuthorizationManager();