@nextclaw/ui 0.15.1 → 0.15.2

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 (158) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/dist/assets/{api-b1R9VdeH.js → api-B7Qigvtm.js} +2 -2
  3. package/dist/assets/{appearance-settings-page-D2O9pch9.js → appearance-settings-page-CuE0Iiqj.js} +1 -1
  4. package/dist/assets/{book-open-B77lQ2Tg.js → book-open-Ba2f7YiG.js} +1 -1
  5. package/dist/assets/channels-list-page-CTsM6TcP.js +8 -0
  6. package/dist/assets/{chat-page-CZ7WxPir.js → chat-page-C_5dcuE3.js} +1 -1
  7. package/dist/assets/config-split-page-Fbb4PtTf.js +1 -0
  8. package/dist/assets/{confirm-dialog-C-NXSioX.js → confirm-dialog-D2EG3nqE.js} +1 -1
  9. package/dist/assets/{desktop-update-config-usHpXugP.js → desktop-update-config-BGR3HUi1.js} +1 -1
  10. package/dist/assets/{dist-jdGmbb6w.js → dist-7JTjycxE.js} +1 -1
  11. package/dist/assets/{dist-YtoqU-AT.js → dist-wXLhk4hl.js} +1 -1
  12. package/dist/assets/doc-browser-bHyXnxlj.js +1 -0
  13. package/dist/assets/doc-browser-context-CYAGm5d5.js +1 -0
  14. package/dist/assets/doc-browser-gTrQ3jCF.js +1 -0
  15. package/dist/assets/{es2015-bNpL2LKy.js → es2015-j1SRCIX5.js} +1 -1
  16. package/dist/assets/{external-link-BM6fk8q-.js → external-link-DxuPaDoK.js} +1 -1
  17. package/dist/assets/form-actions-BztL7P_T.js +1 -0
  18. package/dist/assets/{i18n-provider-CntWolRA.js → i18n-provider-B4oMxOFx.js} +1 -1
  19. package/dist/assets/index-CvrvblgX.css +1 -0
  20. package/dist/assets/index-Obd4JYU2.js +104 -0
  21. package/dist/assets/loader-circle-CdzgK4eH.js +1 -0
  22. package/dist/assets/{mcp-marketplace-page-CLmUstLN.js → mcp-marketplace-page-Bt7CuOoq.js} +2 -2
  23. package/dist/assets/mcp-marketplace-page-Ck3t78_n.js +1 -0
  24. package/dist/assets/model-config-page-DeKk9gvE.js +1 -0
  25. package/dist/assets/plus-Bx7wQbhG.js +1 -0
  26. package/dist/assets/{provider-scoped-model-input-Cm_a4GHf.js → provider-scoped-model-input-DsH2LF1q.js} +1 -1
  27. package/dist/assets/providers-config-page-BFnGp68F.js +1 -0
  28. package/dist/assets/{react-BN8uzPnp.js → react-UirzUuST.js} +1 -1
  29. package/dist/assets/{refresh-cw-BpVcFPmJ.js → refresh-cw-eT8BF-8B.js} +1 -1
  30. package/dist/assets/remote-CDkHZtA8.js +1 -0
  31. package/dist/assets/rotate-cw-1JqxHTuw.js +1 -0
  32. package/dist/assets/runtime-config-page-BelAqiNG.js +1 -0
  33. package/dist/assets/{save-BpFW5fpk.js → save-Cb2g0BOi.js} +1 -1
  34. package/dist/assets/{search-D0uO7enf.js → search-BcHjwdsb.js} +1 -1
  35. package/dist/assets/search-config-page-zPm6--Ig.js +1 -0
  36. package/dist/assets/secrets-config-page-Cxyp-KuQ.js +3 -0
  37. package/dist/assets/security-config-B_gCq9fn.js +1 -0
  38. package/dist/assets/{select-Bh-CrA4p.js → select-CdSEOwVM.js} +1 -1
  39. package/dist/assets/{setting-row-BJfCHRcb.js → setting-row-CTAtGexr.js} +1 -1
  40. package/dist/assets/{settings-2-Blzi1vr6.js → settings-2-2DWauOjY.js} +1 -1
  41. package/dist/assets/{side-dock-ZdaSrQVB.js → side-dock-DPvakJ0a.js} +2 -2
  42. package/dist/assets/{skeleton-BLlEzebZ.js → skeleton-P5oM14E1.js} +1 -1
  43. package/dist/assets/{switch-CFLJFNSd.js → switch-BU_UEULb.js} +1 -1
  44. package/dist/assets/{tag-chip-CzGihiQx.js → tag-chip-D-WsKoT1.js} +1 -1
  45. package/dist/assets/{tooltip-CdcrBjrC.js → tooltip-CjkOaHW7.js} +1 -1
  46. package/dist/assets/{use-config-CVPgsBmU.js → use-config--hx5oxQn.js} +1 -1
  47. package/dist/assets/{use-confirm-dialog-CSeBk9SF.js → use-confirm-dialog-CD9a_SK1.js} +1 -1
  48. package/dist/assets/x-DT5x45JU.js +1 -0
  49. package/dist/index.html +38 -30
  50. package/index.html +12 -4
  51. package/package.json +9 -9
  52. package/src/app/components/layout/__tests__/sidebar.layout.test.tsx +29 -1
  53. package/src/app/components/layout/sidebar-items.tsx +43 -4
  54. package/src/app/components/layout/sidebar.tsx +83 -61
  55. package/src/app/configs/app-navigation.config.ts +49 -0
  56. package/src/app/styles/design-system.css +82 -0
  57. package/src/features/channels/components/config/channel-form.tsx +14 -11
  58. package/src/features/chat/components/conversation/__tests__/chat-conversation-header-section.test.tsx +40 -0
  59. package/src/features/chat/components/conversation/chat-conversation-header-section.tsx +3 -1
  60. package/src/features/chat/components/conversation/chat-conversation-panel.tsx +1 -1
  61. package/src/features/chat/components/layout/__tests__/chat-sidebar-toolbar.test.tsx +48 -0
  62. package/src/features/chat/components/layout/__tests__/chat-sidebar-utility-menu.test.tsx +55 -0
  63. package/src/features/chat/components/layout/chat-sidebar-desktop-layout.tsx +0 -3
  64. package/src/features/chat/components/layout/chat-sidebar-toolbar.tsx +3 -4
  65. package/src/features/chat/components/layout/chat-sidebar-utility-menu.tsx +29 -30
  66. package/src/features/chat/components/layout/chat-sidebar.tsx +21 -7
  67. package/src/features/chat/features/conversation/components/session-conversation-input.tsx +2 -1
  68. package/src/features/chat/features/message/components/__tests__/chat-inline-panel-app-card.test.tsx +22 -1
  69. package/src/features/chat/features/message/components/chat-inline-panel-app-card.tsx +10 -5
  70. package/src/features/chat/features/message/utils/chat-inline-panel-app-card.utils.ts +0 -11
  71. package/src/features/chat/features/session/components/__tests__/chat-sidebar-project-groups.test.tsx +116 -0
  72. package/src/features/chat/features/session/components/chat-sidebar-project-groups.tsx +108 -46
  73. package/src/features/chat/features/session/components/chat-sidebar-session-entry.tsx +6 -0
  74. package/src/features/chat/features/session/components/chat-sidebar-session-item.tsx +48 -24
  75. package/src/features/chat/features/session/components/chat-sidebar-session-list.tsx +0 -3
  76. package/src/features/chat/features/session/components/session-header/chat-session-header-actions.tsx +0 -4
  77. package/src/features/chat/features/session/utils/chat-sidebar-session-groups.utils.ts +25 -3
  78. package/src/features/chat/features/welcome/components/chat-welcome-capability-grid.tsx +9 -4
  79. package/src/features/chat/features/welcome/components/chat-welcome.tsx +0 -5
  80. package/src/features/chat/features/workspace/components/__tests__/chat-session-workspace-file-preview.test.tsx +159 -56
  81. package/src/features/chat/features/workspace/components/__tests__/chat-session-workspace-panel-content.test.tsx +1 -0
  82. package/src/features/chat/features/workspace/components/__tests__/chat-session-workspace-panel.test.tsx +144 -0
  83. package/src/features/chat/features/workspace/components/chat-session-workspace-directory-browser.tsx +99 -0
  84. package/src/features/chat/features/workspace/components/chat-session-workspace-file-breadcrumbs.tsx +3 -1
  85. package/src/features/chat/features/workspace/components/chat-session-workspace-file-preview.tsx +66 -7
  86. package/src/features/chat/features/workspace/components/chat-session-workspace-panel-content.tsx +3 -0
  87. package/src/features/chat/features/workspace/components/chat-session-workspace-panel-nav.tsx +44 -1
  88. package/src/features/chat/features/workspace/components/chat-session-workspace-panel.tsx +45 -3
  89. package/src/features/chat/managers/__tests__/chat-session-list.manager.test.ts +33 -2
  90. package/src/features/chat/managers/chat-session-list.manager.ts +32 -0
  91. package/src/features/chat/stores/chat-session-list.store.ts +28 -5
  92. package/src/features/marketplace/components/mcp/mcp-marketplace-page.tsx +5 -12
  93. package/src/features/panel-apps/components/panel-app-list-item.tsx +6 -6
  94. package/src/features/panel-apps/index.ts +1 -0
  95. package/src/features/panel-apps/utils/panel-app-doc-browser.utils.tsx +2 -10
  96. package/src/features/panel-apps/utils/panel-app-iframe.utils.ts +6 -0
  97. package/src/features/pwa/managers/__tests__/pwa-shell-theme.manager.test.ts +11 -3
  98. package/src/features/pwa/managers/pwa-shell-theme.manager.ts +3 -1
  99. package/src/features/service-apps/components/service-apps-panel.tsx +145 -35
  100. package/src/features/settings/components/config/provider-auth-section.tsx +1 -1
  101. package/src/features/settings/components/config/provider-form.tsx +11 -8
  102. package/src/features/settings/components/config/provider-models-section.tsx +7 -7
  103. package/src/features/settings/components/config/provider-pill-selector.tsx +1 -1
  104. package/src/features/settings/components/config/secrets-config-form.tsx +5 -4
  105. package/src/features/settings/pages/model-config-page.tsx +26 -25
  106. package/src/features/settings/pages/providers-config-page.tsx +10 -13
  107. package/src/features/settings/pages/search-config-page.tsx +8 -5
  108. package/src/features/system-status/components/config/runtime-config-editor.tsx +5 -4
  109. package/src/index.css +18 -0
  110. package/src/shared/components/config-split-page.tsx +6 -6
  111. package/src/shared/components/doc-browser/__tests__/doc-browser-url.utils.test.ts +73 -0
  112. package/src/shared/components/doc-browser/__tests__/doc-browser.test.tsx +48 -0
  113. package/src/shared/components/doc-browser/doc-browser-context.tsx +4 -0
  114. package/src/shared/components/doc-browser/doc-browser-panel-parts.tsx +5 -1
  115. package/src/shared/components/doc-browser/doc-browser-renderer.types.ts +2 -1
  116. package/src/shared/components/doc-browser/doc-browser-tab-strip.tsx +10 -10
  117. package/src/shared/components/doc-browser/doc-browser.tsx +6 -4
  118. package/src/shared/components/doc-browser/utils/doc-browser-url.utils.ts +62 -4
  119. package/src/shared/components/resizable-right-panel/__tests__/resizable-right-panel.test.tsx +18 -0
  120. package/src/shared/components/resizable-right-panel/resizable-right-panel.tsx +28 -3
  121. package/src/shared/components/ui/actions/__tests__/icon-action-button.test.tsx +36 -0
  122. package/src/shared/components/ui/actions/form-actions.tsx +25 -0
  123. package/src/shared/components/ui/actions/icon-action-button.tsx +25 -2
  124. package/src/shared/components/ui/card.tsx +12 -4
  125. package/src/shared/components/ui/tab-strip/compact-tab-strip.tsx +2 -1
  126. package/src/shared/hooks/use-server-path-browse.ts +26 -4
  127. package/src/shared/hooks/use-server-path-read.ts +11 -1
  128. package/src/shared/lib/api/utils/server-path.utils.ts +1 -0
  129. package/src/shared/lib/i18n/locales/en-US/chat.json +16 -0
  130. package/src/shared/lib/i18n/locales/en-US/core.json +3 -0
  131. package/src/shared/lib/i18n/locales/en-US/doc-browser.json +5 -0
  132. package/src/shared/lib/i18n/locales/zh-CN/chat.json +16 -0
  133. package/src/shared/lib/i18n/locales/zh-CN/core.json +3 -0
  134. package/src/shared/lib/i18n/locales/zh-CN/doc-browser.json +5 -0
  135. package/src/shared/lib/session-project/workspace-file-breadcrumb.utils.ts +9 -5
  136. package/src/shared/lib/theme/index.test.ts +21 -0
  137. package/src/shared/lib/theme/index.ts +6 -4
  138. package/src/vite-env.d.ts +2 -0
  139. package/dist/assets/channels-list-page-DcsIQc6A.js +0 -8
  140. package/dist/assets/config-split-page-BWQ6_Raq.js +0 -1
  141. package/dist/assets/doc-browser-B_Tf9n70.js +0 -1
  142. package/dist/assets/doc-browser-BdZPFOVa.js +0 -1
  143. package/dist/assets/doc-browser-context-DwokemUn.js +0 -1
  144. package/dist/assets/index-BIcEw90u.js +0 -104
  145. package/dist/assets/index-BiNSTKTU.css +0 -1
  146. package/dist/assets/loader-circle-MBgNSVIH.js +0 -1
  147. package/dist/assets/mcp-marketplace-page-rZkmWPMy.js +0 -1
  148. package/dist/assets/model-config-page-Di0ySJD2.js +0 -1
  149. package/dist/assets/plus-vDqW5Kxp.js +0 -1
  150. package/dist/assets/providers-config-page-BAf2W0MV.js +0 -1
  151. package/dist/assets/remote-CLQ0IBM8.js +0 -1
  152. package/dist/assets/rotate-cw-CvNBv7_w.js +0 -1
  153. package/dist/assets/runtime-config-page-CflvvoDc.js +0 -1
  154. package/dist/assets/search-config-page-D0klFUXd.js +0 -1
  155. package/dist/assets/secrets-config-page-dVZ631Wp.js +0 -3
  156. package/dist/assets/security-config-C5gnlkuj.js +0 -1
  157. package/dist/assets/x-BF2KQ--V.js +0 -1
  158. /package/dist/assets/{config-hints-fGnUjDe9.js → config-hints-C_iP1zl_.js} +0 -0
@@ -0,0 +1,36 @@
1
+ import { render, screen } from '@testing-library/react';
2
+ import { describe, expect, it } from 'vitest';
3
+ import { IconActionButton } from '@/shared/components/ui/actions/icon-action-button';
4
+
5
+ describe('IconActionButton', () => {
6
+ it('applies compact size and strong tone classes for nested hover surfaces', () => {
7
+ render(
8
+ <IconActionButton
9
+ size="sm"
10
+ tone="strong"
11
+ icon={<span data-testid="icon" />}
12
+ label="Pin"
13
+ />,
14
+ );
15
+
16
+ const button = screen.getByRole('button', { name: 'Pin' });
17
+ expect(button.className).toContain('h-6');
18
+ expect(button.className).toContain('w-6');
19
+ expect(button.className).toContain('p-1');
20
+ expect(button.className).toContain('hover:bg-black/10');
21
+ });
22
+
23
+ it('defaults to medium size and soft accent hover', () => {
24
+ render(
25
+ <IconActionButton
26
+ icon={<span data-testid="icon" />}
27
+ label="More"
28
+ />,
29
+ );
30
+
31
+ const button = screen.getByRole('button', { name: 'More' });
32
+ expect(button.className).toContain('h-7');
33
+ expect(button.className).toContain('w-7');
34
+ expect(button.className).toContain('hover:bg-accent');
35
+ });
36
+ });
@@ -0,0 +1,25 @@
1
+ import type { HTMLAttributes } from 'react';
2
+ import { cn } from '@/shared/lib/utils';
3
+
4
+ type FormActionsProps = HTMLAttributes<HTMLDivElement> & {
5
+ align?: 'start' | 'end' | 'between';
6
+ };
7
+
8
+ export function FormActions({
9
+ align = 'end',
10
+ className,
11
+ ...props
12
+ }: FormActionsProps) {
13
+ return (
14
+ <div
15
+ className={cn(
16
+ 'flex flex-wrap items-center gap-2',
17
+ align === 'start' && 'justify-start',
18
+ align === 'end' && 'justify-end',
19
+ align === 'between' && 'justify-between',
20
+ className,
21
+ )}
22
+ {...props}
23
+ />
24
+ );
25
+ }
@@ -7,16 +7,35 @@ import {
7
7
  } from '@/shared/components/ui/tooltip';
8
8
  import { cn } from '@/shared/lib/utils';
9
9
 
10
+ type IconActionButtonSize = 'sm' | 'md' | 'lg';
11
+ type IconActionButtonTone = 'default' | 'strong';
12
+
10
13
  type IconActionButtonProps = Omit<
11
14
  React.ButtonHTMLAttributes<HTMLButtonElement>,
12
15
  'children' | 'type'
13
16
  > & {
14
17
  icon: React.ReactNode;
15
18
  label: string;
19
+ size?: IconActionButtonSize;
20
+ /** default: soft accent hover. strong: denser gray hover for nested hover surfaces. */
21
+ tone?: IconActionButtonTone;
16
22
  tooltip?: string | false | null;
17
23
  tooltipSide?: 'top' | 'right' | 'bottom' | 'left';
18
24
  };
19
25
 
26
+ const SIZE_CLASS: Record<IconActionButtonSize, string> = {
27
+ sm: 'h-6 w-6 rounded-md p-1',
28
+ md: 'h-7 w-7 rounded-md p-1.5',
29
+ lg: 'h-8 w-8 rounded-lg p-1.5',
30
+ };
31
+
32
+ const TONE_CLASS: Record<IconActionButtonTone, string> = {
33
+ default:
34
+ 'text-muted-foreground hover:bg-accent hover:text-accent-foreground disabled:text-muted-foreground/45 disabled:hover:bg-transparent disabled:hover:text-muted-foreground/45',
35
+ strong:
36
+ 'text-muted-foreground hover:bg-black/10 hover:text-foreground disabled:text-muted-foreground/45 disabled:hover:bg-transparent disabled:hover:text-muted-foreground/45',
37
+ };
38
+
20
39
  const IconActionButton = React.forwardRef<HTMLButtonElement, IconActionButtonProps>(
21
40
  (
22
41
  {
@@ -24,6 +43,8 @@ const IconActionButton = React.forwardRef<HTMLButtonElement, IconActionButtonPro
24
43
  disabled = false,
25
44
  icon,
26
45
  label,
46
+ size = 'md',
47
+ tone = 'default',
27
48
  tooltip,
28
49
  tooltipSide = 'bottom',
29
50
  ...buttonProps
@@ -38,8 +59,9 @@ const IconActionButton = React.forwardRef<HTMLButtonElement, IconActionButtonPro
38
59
  disabled={disabled}
39
60
  aria-label={label}
40
61
  className={cn(
41
- 'inline-flex h-7 w-7 shrink-0 items-center justify-center rounded-md transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/40 disabled:cursor-not-allowed',
42
- 'text-muted-foreground hover:bg-accent hover:text-accent-foreground disabled:text-muted-foreground/45 disabled:hover:bg-transparent disabled:hover:text-muted-foreground/45',
62
+ 'inline-flex shrink-0 items-center justify-center transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/40 disabled:cursor-not-allowed',
63
+ SIZE_CLASS[size],
64
+ TONE_CLASS[tone],
43
65
  className
44
66
  )}
45
67
  >
@@ -63,3 +85,4 @@ const IconActionButton = React.forwardRef<HTMLButtonElement, IconActionButtonPro
63
85
  IconActionButton.displayName = 'IconActionButton';
64
86
 
65
87
  export { IconActionButton };
88
+ export type { IconActionButtonProps, IconActionButtonSize, IconActionButtonTone };
@@ -3,13 +3,21 @@ import { cn } from '@/shared/lib/utils';
3
3
 
4
4
  const Card = React.forwardRef<
5
5
  HTMLDivElement,
6
- React.HTMLAttributes<HTMLDivElement> & { hover?: boolean }
7
- >(({ className, hover = true, ...props }, ref) => (
6
+ React.HTMLAttributes<HTMLDivElement> & {
7
+ hover?: boolean;
8
+ /** Flat settings-style surface: light border, no shadow. */
9
+ surface?: 'raised' | 'flat';
10
+ }
11
+ >(({ className, hover = true, surface = 'raised', ...props }, ref) => (
8
12
  <div
9
13
  ref={ref}
10
14
  className={cn(
11
- 'rounded-2xl border border-border bg-card text-card-foreground shadow-card transition-all duration-base',
12
- hover && 'hover:border-primary/25 hover:shadow-card-hover',
15
+ 'rounded-2xl border bg-card text-card-foreground transition-colors duration-base',
16
+ surface === 'raised'
17
+ ? 'border-border shadow-card'
18
+ : 'border-border/75 shadow-none',
19
+ hover && surface === 'raised' && 'hover:border-primary/25 hover:shadow-card-hover',
20
+ hover && surface === 'flat' && 'hover:bg-muted/40',
13
21
  className
14
22
  )}
15
23
  {...props}
@@ -147,11 +147,12 @@ function CompactTabItem({
147
147
  <CompactTabLabelButton tab={tab} labelClassName={labelClassName} />
148
148
  {tab.onClose && tab.closePlacement !== "leading-hover" ? (
149
149
  <IconActionButton
150
+ size="sm"
151
+ tone="strong"
150
152
  icon={<X className="h-3 w-3" />}
151
153
  label={tab.closeLabel ?? ""}
152
154
  tooltip={tab.closeLabel ?? null}
153
155
  onClick={(event) => closeCompactTab(event, tab.onClose!)}
154
- className="h-5 w-5 rounded p-0.5 hover:bg-black/10"
155
156
  />
156
157
  ) : null}
157
158
  </div>
@@ -1,19 +1,41 @@
1
1
  import { useQuery } from '@tanstack/react-query';
2
2
  import { fetchServerPathBrowse } from '@/shared/lib/api';
3
3
 
4
+ export function buildServerPathBrowseQueryKey(params: {
5
+ path?: string | null;
6
+ basePath?: string | null;
7
+ includeFiles?: boolean;
8
+ }) {
9
+ return [
10
+ 'server-path-browse',
11
+ params.path?.trim() ?? '',
12
+ params.basePath?.trim() ?? '',
13
+ params.includeFiles ?? false,
14
+ ] as const;
15
+ }
16
+
4
17
  export function useServerPathBrowse(params: {
5
18
  path?: string | null;
19
+ basePath?: string | null;
6
20
  includeFiles?: boolean;
7
21
  enabled?: boolean;
8
22
  }) {
23
+ const { basePath, enabled, includeFiles, path } = params;
24
+ const normalizedPath = path?.trim() ?? '';
25
+ const normalizedBasePath = basePath?.trim() ?? '';
9
26
  return useQuery({
10
- queryKey: ['server-path-browse', params.path ?? null, params.includeFiles ?? false],
27
+ queryKey: buildServerPathBrowseQueryKey({
28
+ path: normalizedPath,
29
+ basePath: normalizedBasePath,
30
+ includeFiles,
31
+ }),
11
32
  queryFn: () =>
12
33
  fetchServerPathBrowse({
13
- path: params.path,
14
- includeFiles: params.includeFiles,
34
+ path: normalizedPath,
35
+ basePath: normalizedBasePath,
36
+ includeFiles,
15
37
  }),
16
- enabled: params.enabled ?? true,
38
+ enabled: enabled ?? true,
17
39
  staleTime: 0,
18
40
  });
19
41
  }
@@ -1,6 +1,13 @@
1
1
  import { useQuery } from '@tanstack/react-query';
2
2
  import { fetchServerPathRead } from '@/shared/lib/api';
3
3
 
4
+ export function buildServerPathReadQueryKey(params: {
5
+ path?: string | null;
6
+ basePath?: string | null;
7
+ }) {
8
+ return ['server-path-read', params.path?.trim() ?? '', params.basePath ?? null] as const;
9
+ }
10
+
4
11
  export function useServerPathRead(params: {
5
12
  path?: string | null;
6
13
  basePath?: string | null;
@@ -8,7 +15,10 @@ export function useServerPathRead(params: {
8
15
  }) {
9
16
  const normalizedPath = params.path?.trim() ?? '';
10
17
  return useQuery({
11
- queryKey: ['server-path-read', normalizedPath, params.basePath ?? null],
18
+ queryKey: buildServerPathReadQueryKey({
19
+ path: normalizedPath,
20
+ basePath: params.basePath,
21
+ }),
12
22
  queryFn: () =>
13
23
  fetchServerPathRead({
14
24
  path: normalizedPath,
@@ -17,6 +17,7 @@ function encodeServerPathSegments(path: string): string {
17
17
 
18
18
  export async function fetchServerPathBrowse(params?: {
19
19
  path?: string | null;
20
+ basePath?: string | null;
20
21
  includeFiles?: boolean;
21
22
  }): Promise<ServerPathBrowseView> {
22
23
  return await nextclawClient.serverPaths.browse(params);
@@ -41,6 +41,9 @@
41
41
  "chatChildSessionEmpty": "No child session messages yet.",
42
42
  "chatWorkspaceBack": "Back in workspace",
43
43
  "chatWorkspaceForward": "Forward in workspace",
44
+ "chatWorkspaceRefreshFile": "Refresh preview",
45
+ "chatWorkspaceMaximizePanel": "Maximize workspace panel",
46
+ "chatWorkspaceRestorePanel": "Restore workspace panel",
44
47
  "chatWorkspaceClosePanel": "Close workspace panel",
45
48
  "chatWorkspaceSideChatDraftTitle": "Side chat",
46
49
  "chatWorkspaceSideChatDraftSubtitle": "This draft will inherit the parent session context when you send the first message.",
@@ -49,7 +52,13 @@
49
52
  "chatWorkspacePreview": "Preview",
50
53
  "chatWorkspaceDiff": "Diff",
51
54
  "chatWorkspaceCloseFile": "Close file",
55
+ "chatWorkspaceLoadingPreview": "Loading preview…",
52
56
  "chatWorkspaceLoadingFile": "Loading file…",
57
+ "chatWorkspaceLoadingDirectory": "Loading directory…",
58
+ "chatWorkspaceOpenDirectory": "Open directory",
59
+ "chatWorkspaceOpenFile": "Open file",
60
+ "chatWorkspaceDirectoryEmpty": "This directory is empty.",
61
+ "chatWorkspaceDirectoryLoadFailed": "Failed to load directory",
53
62
  "chatWorkspacePreviewUnsupported": "This file is not supported in the sidebar preview yet.",
54
63
  "chatWorkspacePreviewEmpty": "No file content is available for preview.",
55
64
  "chatWorkspaceDiffEmpty": "No diff content is available.",
@@ -199,6 +208,13 @@
199
208
  "chatSidebarYesterday": "Yesterday",
200
209
  "chatSidebarPrevious7Days": "Previous 7 Days",
201
210
  "chatSidebarOlder": "Older",
211
+ "chatSidebarPinned": "Pinned",
212
+ "chatSidebarPinSession": "Pin session",
213
+ "chatSidebarUnpinSession": "Unpin session",
214
+ "chatSidebarPinProject": "Pin project",
215
+ "chatSidebarUnpinProject": "Unpin project",
216
+ "chatSidebarExpandProject": "Expand project",
217
+ "chatSidebarCollapseProject": "Collapse project",
202
218
  "chatSidebarProjectViewEmpty": "No project conversations yet",
203
219
  "chatWelcomeTitle": "Hello, how can I help you?",
204
220
  "chatWelcomeSubtitle": "Describe the goal, choose the project context, and let NextClaw take it from there.",
@@ -11,6 +11,8 @@
11
11
  "marketplace": "Marketplace",
12
12
  "advanced": "Advanced",
13
13
  "settings": "Settings",
14
+ "settingsGroupBasic": "Basic Configuration",
15
+ "settingsGroupAdvanced": "Advanced Configuration",
14
16
  "appearance": "Appearance",
15
17
  "settingsMenu": "Settings menu",
16
18
  "sidebarCollapse": "Collapse sidebar",
@@ -41,6 +43,7 @@
41
43
  "language": "Language",
42
44
  "theme": "Theme",
43
45
  "themeNatural": "Natural",
46
+ "themeWork": "Default",
44
47
  "themeMinimal": "Minimal",
45
48
  "themeWarm": "Paper",
46
49
  "themeCool": "Mist",
@@ -65,6 +65,8 @@
65
65
  "serviceAppsLoading": "Loading service apps...",
66
66
  "serviceAppsLoadFailed": "Failed to load service apps",
67
67
  "serviceAppsEmpty": "No service apps yet",
68
+ "serviceAppsEmptyTitle": "No service apps",
69
+ "serviceAppsEmptyDescription": "Service apps expose local runtimes and actions that panel apps can call.",
68
70
  "serviceAppsRefresh": "Refresh service apps",
69
71
  "serviceAppsDiscoverActions": "Connect and discover actions",
70
72
  "serviceAppsDiscoverActionsHint": "Connect to the service app runtime and discover its available actions.",
@@ -84,6 +86,9 @@
84
86
  "serviceAppsStatusHint_running": "The service app runtime is connected and ready for action discovery or invocation.",
85
87
  "serviceAppsStatusHint_failed": "The runtime connection failed; check the error or disconnect and try again.",
86
88
  "serviceAppsStatusHint_stopped": "The service app is stopped.",
89
+ "serviceAppsActionsSection": "Actions",
90
+ "serviceAppsActionsEmpty": "No actions discovered yet",
91
+ "serviceAppsDetails": "Runtime details",
87
92
  "serviceAppsGrantedTo": "Granted to",
88
93
  "serviceAppsRevokeGrant": "Revoke grant",
89
94
  "serviceAppsRuntimeState_matched": "Matched",
@@ -41,6 +41,9 @@
41
41
  "chatChildSessionEmpty": "子会话还没有消息。",
42
42
  "chatWorkspaceBack": "工作区后退",
43
43
  "chatWorkspaceForward": "工作区前进",
44
+ "chatWorkspaceRefreshFile": "刷新预览",
45
+ "chatWorkspaceMaximizePanel": "最大化工作区侧栏",
46
+ "chatWorkspaceRestorePanel": "还原工作区侧栏",
44
47
  "chatWorkspaceClosePanel": "关闭工作区侧栏",
45
48
  "chatWorkspaceSideChatDraftTitle": "Side chat",
46
49
  "chatWorkspaceSideChatDraftSubtitle": "发送第一条消息后,会继承父会话上下文并创建真实 child 会话。",
@@ -49,7 +52,13 @@
49
52
  "chatWorkspacePreview": "预览",
50
53
  "chatWorkspaceDiff": "Diff",
51
54
  "chatWorkspaceCloseFile": "关闭文件",
55
+ "chatWorkspaceLoadingPreview": "正在加载预览…",
52
56
  "chatWorkspaceLoadingFile": "正在加载文件…",
57
+ "chatWorkspaceLoadingDirectory": "正在加载目录…",
58
+ "chatWorkspaceOpenDirectory": "打开目录",
59
+ "chatWorkspaceOpenFile": "打开文件",
60
+ "chatWorkspaceDirectoryEmpty": "这个目录为空。",
61
+ "chatWorkspaceDirectoryLoadFailed": "读取目录失败",
53
62
  "chatWorkspacePreviewUnsupported": "该文件暂不支持在侧栏预览。",
54
63
  "chatWorkspacePreviewEmpty": "当前没有可显示的文件内容。",
55
64
  "chatWorkspaceDiffEmpty": "当前没有可显示的 diff 内容。",
@@ -199,6 +208,13 @@
199
208
  "chatSidebarYesterday": "昨天",
200
209
  "chatSidebarPrevious7Days": "近 7 天",
201
210
  "chatSidebarOlder": "更早",
211
+ "chatSidebarPinned": "已置顶",
212
+ "chatSidebarPinSession": "置顶会话",
213
+ "chatSidebarUnpinSession": "取消置顶会话",
214
+ "chatSidebarPinProject": "置顶项目",
215
+ "chatSidebarUnpinProject": "取消置顶项目",
216
+ "chatSidebarExpandProject": "展开项目",
217
+ "chatSidebarCollapseProject": "收起项目",
202
218
  "chatSidebarProjectViewEmpty": "还没有绑定项目的会话",
203
219
  "chatWelcomeTitle": "你好,有什么可以帮你的吗?",
204
220
  "chatWelcomeSubtitle": "描述目标,选择项目上下文,让 NextClaw 接住这次任务。",
@@ -11,6 +11,8 @@
11
11
  "marketplace": "市场",
12
12
  "advanced": "高级",
13
13
  "settings": "设置",
14
+ "settingsGroupBasic": "基础配置",
15
+ "settingsGroupAdvanced": "高级配置",
14
16
  "appearance": "外观",
15
17
  "settingsMenu": "设置菜单",
16
18
  "sidebarCollapse": "收起侧边栏",
@@ -41,6 +43,7 @@
41
43
  "language": "语言",
42
44
  "theme": "主题",
43
45
  "themeNatural": "自然",
46
+ "themeWork": "默认",
44
47
  "themeMinimal": "简白",
45
48
  "themeWarm": "纸暖",
46
49
  "themeCool": "雾蓝",
@@ -65,6 +65,8 @@
65
65
  "serviceAppsLoading": "加载服务应用中...",
66
66
  "serviceAppsLoadFailed": "服务应用加载失败",
67
67
  "serviceAppsEmpty": "暂无服务应用",
68
+ "serviceAppsEmptyTitle": "暂无服务应用",
69
+ "serviceAppsEmptyDescription": "服务应用提供本地运行时与动作,供面板应用调用。",
68
70
  "serviceAppsRefresh": "刷新服务应用",
69
71
  "serviceAppsDiscoverActions": "连接并发现动作",
70
72
  "serviceAppsDiscoverActionsHint": "连接服务应用运行时,并发现它当前可用的动作。",
@@ -84,6 +86,9 @@
84
86
  "serviceAppsStatusHint_running": "服务应用运行时已连接,可用于发现或调用动作。",
85
87
  "serviceAppsStatusHint_failed": "运行时连接失败,请查看错误信息或断开连接后重试。",
86
88
  "serviceAppsStatusHint_stopped": "服务应用已停止。",
89
+ "serviceAppsActionsSection": "动作",
90
+ "serviceAppsActionsEmpty": "尚未发现动作",
91
+ "serviceAppsDetails": "运行时详情",
87
92
  "serviceAppsGrantedTo": "已授权给",
88
93
  "serviceAppsRevokeGrant": "撤销授权",
89
94
  "serviceAppsRuntimeState_matched": "已匹配",
@@ -131,12 +131,13 @@ function readRelativeSegments(params: {
131
131
  function buildSegmentsFromLabels(params: {
132
132
  labels: string[];
133
133
  basePath?: string | null;
134
+ currentKind?: "file" | "directory";
134
135
  leading?: WorkspaceFileBreadcrumbSegmentViewModel | null;
135
136
  }): WorkspaceFileBreadcrumbSegmentViewModel[] {
136
- const { basePath = null, labels, leading = null } = params;
137
+ const { basePath = null, currentKind = "file", labels, leading = null } = params;
137
138
  const items = labels.map<WorkspaceFileBreadcrumbSegmentViewModel>(
138
139
  (label, index) => {
139
- const kind = index === labels.length - 1 ? "file" : "directory";
140
+ const kind = index === labels.length - 1 ? currentKind : "directory";
140
141
  const path = joinPathSegments(basePath, labels.slice(0, index + 1));
141
142
  return {
142
143
  key: `${index}:${label}`,
@@ -167,12 +168,13 @@ function buildLocationLabel(params: {
167
168
 
168
169
  export function buildWorkspaceFileBreadcrumb(params: {
169
170
  path: string;
171
+ kind?: "file" | "directory";
170
172
  sessionProjectRoot: string | null;
171
173
  line?: number | null;
172
174
  column?: number | null;
173
175
  truncated: boolean;
174
176
  }): WorkspaceFileBreadcrumbViewModel {
175
- const { column, line, path, sessionProjectRoot, truncated } = params;
177
+ const { column, kind = "file", line, path, sessionProjectRoot, truncated } = params;
176
178
  const fullPath = path.trim();
177
179
  const relativeSegments =
178
180
  sessionProjectRoot?.trim() && fullPath
@@ -192,6 +194,7 @@ export function buildWorkspaceFileBreadcrumb(params: {
192
194
  segments = buildSegmentsFromLabels({
193
195
  labels: relativeSegments,
194
196
  basePath: workspacePath,
197
+ currentKind: kind,
195
198
  leading: {
196
199
  key: `workspace:${workspaceLabel}`,
197
200
  label: workspaceLabel,
@@ -207,6 +210,7 @@ export function buildWorkspaceFileBreadcrumb(params: {
207
210
  segments = buildSegmentsFromLabels({
208
211
  labels,
209
212
  basePath: rootPath,
213
+ currentKind: kind,
210
214
  leading: prefix
211
215
  ? {
212
216
  key: `root:${prefix}`,
@@ -225,9 +229,9 @@ export function buildWorkspaceFileBreadcrumb(params: {
225
229
  {
226
230
  key: "file:unknown",
227
231
  label: fullPath || "file",
228
- kind: "file",
232
+ kind,
229
233
  path: fullPath,
230
- browsePath: readParentPath(fullPath),
234
+ browsePath: kind === "directory" ? fullPath : readParentPath(fullPath),
231
235
  isCurrent: true,
232
236
  },
233
237
  ];
@@ -0,0 +1,21 @@
1
+ import { beforeEach, describe, expect, it } from 'vitest';
2
+ import {
3
+ DEFAULT_UI_THEME,
4
+ resolveInitialTheme,
5
+ THEME_OPTIONS,
6
+ } from './index';
7
+
8
+ describe('ui theme defaults', () => {
9
+ beforeEach(() => {
10
+ window.localStorage.clear();
11
+ });
12
+
13
+ it('uses the default theme when no preference is saved', () => {
14
+ expect(DEFAULT_UI_THEME).toBe('work');
15
+ expect(resolveInitialTheme()).toBe(DEFAULT_UI_THEME);
16
+ });
17
+
18
+ it('lists the default theme first', () => {
19
+ expect(THEME_OPTIONS[0]?.value).toBe(DEFAULT_UI_THEME);
20
+ });
21
+ });
@@ -1,5 +1,6 @@
1
1
  export type UiTheme =
2
2
  | 'natural'
3
+ | 'work'
3
4
  | 'minimal'
4
5
  | 'warm'
5
6
  | 'cool'
@@ -16,9 +17,10 @@ type UiThemeDefinition = {
16
17
  };
17
18
 
18
19
  const THEME_STORAGE_KEY = 'nextclaw.ui.theme';
19
- const DEFAULT_THEME: UiTheme = 'natural';
20
+ export const DEFAULT_UI_THEME: UiTheme = 'work';
20
21
 
21
22
  const THEME_DEFINITIONS: readonly UiThemeDefinition[] = [
23
+ { value: 'work', labelKey: 'themeWork', appearance: 'light' },
22
24
  { value: 'night', labelKey: 'themeNight', appearance: 'dark' },
23
25
  { value: 'natural', labelKey: 'themeNatural', appearance: 'light' },
24
26
  { value: 'minimal', labelKey: 'themeMinimal', appearance: 'light' },
@@ -59,13 +61,13 @@ export function getThemeAppearance(theme: UiTheme): UiThemeAppearance {
59
61
  }
60
62
 
61
63
  class UiThemeOwner {
62
- private activeTheme: UiTheme = DEFAULT_THEME;
64
+ private activeTheme: UiTheme = DEFAULT_UI_THEME;
63
65
  private initialized = false;
64
66
  private readonly listeners = new Set<(theme: UiTheme) => void>();
65
67
 
66
68
  resolveInitialTheme = (): UiTheme => {
67
69
  if (typeof window === 'undefined') {
68
- return DEFAULT_THEME;
70
+ return DEFAULT_UI_THEME;
69
71
  }
70
72
 
71
73
  try {
@@ -78,7 +80,7 @@ class UiThemeOwner {
78
80
  // ignore storage failures
79
81
  }
80
82
 
81
- return DEFAULT_THEME;
83
+ return DEFAULT_UI_THEME;
82
84
  };
83
85
 
84
86
  initializeTheme = (): UiTheme => {
package/src/vite-env.d.ts CHANGED
@@ -7,6 +7,8 @@ export {};
7
7
  interface ImportMetaEnv {
8
8
  readonly VITE_API_BASE?: string;
9
9
  readonly VITE_DEV_PROXY_API_BASE?: string;
10
+ readonly VITE_NEXTCLAW_DOCS_BASE_URL?: string;
11
+ readonly VITE_NEXTCLAW_DOCS_CN_BASE_URL?: string;
10
12
  }
11
13
 
12
14
  interface ImportMeta {