fix: move mcp config copy to settings
This commit is contained in:
@@ -1643,7 +1643,6 @@ function App() {
|
||||
onInitializeProperties={handleInitializeProperties}
|
||||
showAIChat={dialogs.showAIChat}
|
||||
onToggleAIChat={dialogs.toggleAIChat}
|
||||
onCopyMcpConfig={handleCopyMcpConfig}
|
||||
vaultPath={resolvedPath}
|
||||
noteList={aiNoteList}
|
||||
noteListFilter={aiNoteListFilter}
|
||||
@@ -1726,7 +1725,7 @@ function App() {
|
||||
onCommit={conflictResolver.commitResolution}
|
||||
onClose={conflictFlow.handleCloseConflictResolver}
|
||||
/>
|
||||
<SettingsPanel open={dialogs.showSettings} settings={settings} aiAgentsStatus={aiAgentsStatus} locale={appLocale} systemLocale={systemLocale} isGitVault={isGitVault} onSave={saveSettings} explicitOrganizationEnabled={explicitOrganizationEnabled} onSaveExplicitOrganization={handleSaveExplicitOrganization} onClose={dialogs.closeSettings} />
|
||||
<SettingsPanel open={dialogs.showSettings} settings={settings} aiAgentsStatus={aiAgentsStatus} locale={appLocale} systemLocale={systemLocale} isGitVault={isGitVault} onSave={saveSettings} onCopyMcpConfig={handleCopyMcpConfig} explicitOrganizationEnabled={explicitOrganizationEnabled} onSaveExplicitOrganization={handleSaveExplicitOrganization} onClose={dialogs.closeSettings} />
|
||||
<FeedbackDialog open={showFeedback} onClose={closeFeedback} />
|
||||
<McpSetupDialog open={showMcpSetupDialog} status={mcpStatus} busyAction={mcpDialogAction} manualConfigSnippet={mcpConfigSnippet} manualConfigLoading={mcpConfigLoading} manualConfigError={mcpConfigError} locale={appLocale} onClose={closeMcpSetupDialog} onConnect={handleConnectMcp} onCopyManualConfig={handleCopyMcpConfig} onDisconnect={handleDisconnectMcp} onLoadManualConfig={handleLoadMcpConfigSnippet} />
|
||||
<CloneVaultModal key={dialogs.showCloneVault ? 'clone-open' : 'clone-closed'} open={dialogs.showCloneVault} onClose={dialogs.closeCloneVault} onVaultCloned={vaultSwitcher.handleVaultCloned} />
|
||||
|
||||
@@ -201,14 +201,10 @@ describe('AiPanel', () => {
|
||||
expect(mockClearConversation).toHaveBeenCalledOnce()
|
||||
})
|
||||
|
||||
it('copies the MCP config from the AI panel header action', () => {
|
||||
const onCopyMcpConfig = vi.fn()
|
||||
render(<AiPanel onClose={vi.fn()} onCopyMcpConfig={onCopyMcpConfig} vaultPath="/tmp/vault" />)
|
||||
it('keeps the MCP config action out of the AI panel header', () => {
|
||||
render(<AiPanel onClose={vi.fn()} vaultPath="/tmp/vault" />)
|
||||
|
||||
expect(screen.getByText('MCP config')).toBeVisible()
|
||||
fireEvent.click(screen.getByRole('button', { name: 'Copy MCP config' }))
|
||||
|
||||
expect(onCopyMcpConfig).toHaveBeenCalledOnce()
|
||||
expect(screen.queryByRole('button', { name: 'Copy MCP config' })).toBeNull()
|
||||
})
|
||||
|
||||
it('renders empty state without context', () => {
|
||||
|
||||
@@ -22,7 +22,6 @@ export type { AiAgentMessage } from '../hooks/useCliAiAgent'
|
||||
|
||||
interface AiPanelProps {
|
||||
onClose: () => void
|
||||
onCopyMcpConfig?: () => void
|
||||
onOpenNote?: (path: string) => void
|
||||
onUnsupportedAiPaste?: (message: string) => void
|
||||
defaultAiAgent?: AiAgentId
|
||||
@@ -45,7 +44,6 @@ interface AiPanelProps {
|
||||
interface AiPanelViewProps {
|
||||
controller: AiPanelController
|
||||
onClose: () => void
|
||||
onCopyMcpConfig?: () => void
|
||||
onOpenNote?: (path: string) => void
|
||||
onUnsupportedAiPaste?: (message: string) => void
|
||||
defaultAiAgent?: AiAgentId
|
||||
@@ -63,7 +61,6 @@ function readinessFromReadyFlag(ready: boolean | undefined): AiAgentReadiness {
|
||||
export function AiPanelView({
|
||||
controller,
|
||||
onClose,
|
||||
onCopyMcpConfig,
|
||||
onOpenNote,
|
||||
onUnsupportedAiPaste,
|
||||
defaultAiAgent: providedDefaultAiAgent,
|
||||
@@ -126,7 +123,6 @@ export function AiPanelView({
|
||||
permissionModeDisabled={isActive}
|
||||
onPermissionModeChange={handlePermissionModeChange}
|
||||
onClose={onClose}
|
||||
onCopyMcpConfig={onCopyMcpConfig}
|
||||
onNewChat={handleNewChat}
|
||||
/>
|
||||
{activeEntry && (
|
||||
@@ -160,7 +156,6 @@ export function AiPanelView({
|
||||
|
||||
export function AiPanel({
|
||||
onClose,
|
||||
onCopyMcpConfig,
|
||||
onOpenNote,
|
||||
onUnsupportedAiPaste,
|
||||
defaultAiAgent: providedDefaultAiAgent,
|
||||
@@ -202,7 +197,6 @@ export function AiPanel({
|
||||
<AiPanelView
|
||||
controller={controller}
|
||||
onClose={onClose}
|
||||
onCopyMcpConfig={onCopyMcpConfig}
|
||||
onOpenNote={onOpenNote}
|
||||
onUnsupportedAiPaste={onUnsupportedAiPaste}
|
||||
defaultAiAgent={providedDefaultAiAgent}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { useEffect, useRef } from 'react'
|
||||
import { Robot, X, PaperPlaneRight, Plus, Link } from '@phosphor-icons/react'
|
||||
import { Copy } from 'lucide-react'
|
||||
import { AiMessage } from './AiMessage'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { ActionTooltip } from '@/components/ui/action-tooltip'
|
||||
@@ -25,7 +24,6 @@ interface AiPanelHeaderProps {
|
||||
permissionModeDisabled: boolean
|
||||
onPermissionModeChange: (mode: AiAgentPermissionMode) => void
|
||||
onClose: () => void
|
||||
onCopyMcpConfig?: () => void
|
||||
onNewChat: () => void
|
||||
}
|
||||
|
||||
@@ -174,7 +172,6 @@ export function AiPanelHeader({
|
||||
permissionModeDisabled,
|
||||
onPermissionModeChange,
|
||||
onClose,
|
||||
onCopyMcpConfig,
|
||||
onNewChat,
|
||||
}: AiPanelHeaderProps) {
|
||||
const t = createTranslator(locale)
|
||||
@@ -195,21 +192,6 @@ export function AiPanelHeader({
|
||||
{headerStatusText({ agentLabel, agentReadiness, modeLabel, t })}
|
||||
</span>
|
||||
</div>
|
||||
{onCopyMcpConfig ? (
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
size="xs"
|
||||
onClick={onCopyMcpConfig}
|
||||
className="h-7 gap-1.5 px-2 text-[11px]"
|
||||
aria-label={t('ai.panel.copyMcpConfig')}
|
||||
title={t('ai.panel.copyMcpConfig')}
|
||||
data-testid="ai-copy-mcp-config"
|
||||
>
|
||||
<Copy size={15} />
|
||||
<span>{t('ai.panel.mcpConfig')}</span>
|
||||
</Button>
|
||||
) : null}
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
|
||||
@@ -67,7 +67,6 @@ interface EditorProps {
|
||||
onInitializeProperties?: (path: string) => void
|
||||
showAIChat?: boolean
|
||||
onToggleAIChat?: () => void
|
||||
onCopyMcpConfig?: () => void
|
||||
vaultPath?: string
|
||||
noteList?: NoteListItem[]
|
||||
noteListFilter?: { type: string | null; query: string }
|
||||
@@ -339,7 +338,6 @@ function EditorLayout({
|
||||
showDiffToggle,
|
||||
showAIChat,
|
||||
onToggleAIChat,
|
||||
onCopyMcpConfig,
|
||||
inspectorCollapsed,
|
||||
onToggleInspector,
|
||||
onNavigateWikilink,
|
||||
@@ -402,7 +400,6 @@ function EditorLayout({
|
||||
showDiffToggle: boolean
|
||||
showAIChat?: boolean
|
||||
onToggleAIChat?: () => void
|
||||
onCopyMcpConfig?: () => void
|
||||
inspectorCollapsed: boolean
|
||||
onToggleInspector: () => void
|
||||
onNavigateWikilink: (target: string) => void
|
||||
@@ -523,7 +520,6 @@ function EditorLayout({
|
||||
noteListFilter={noteListFilter}
|
||||
onToggleInspector={onToggleInspector}
|
||||
onToggleAIChat={onToggleAIChat}
|
||||
onCopyMcpConfig={onCopyMcpConfig}
|
||||
onNavigateWikilink={onNavigateWikilink}
|
||||
onViewCommitDiff={handleViewCommitDiff}
|
||||
onUpdateFrontmatter={onUpdateFrontmatter}
|
||||
@@ -555,7 +551,6 @@ export const Editor = memo(function Editor(props: EditorProps) {
|
||||
inspectorEntry, inspectorContent, gitHistory,
|
||||
onUpdateFrontmatter, onDeleteProperty, onAddProperty, onCreateMissingType, onCreateAndOpenNote, onInitializeProperties,
|
||||
showAIChat, onToggleAIChat,
|
||||
onCopyMcpConfig,
|
||||
vaultPath, noteList, noteListFilter,
|
||||
onToggleFavorite, onToggleOrganized, onRevealFile, onCopyFilePath, onOpenExternalFile,
|
||||
onDeleteNote, onArchiveNote, onUnarchiveNote,
|
||||
@@ -616,7 +611,6 @@ export const Editor = memo(function Editor(props: EditorProps) {
|
||||
showDiffToggle={showDiffToggle}
|
||||
showAIChat={showAIChat}
|
||||
onToggleAIChat={onToggleAIChat}
|
||||
onCopyMcpConfig={onCopyMcpConfig}
|
||||
inspectorCollapsed={inspectorCollapsed}
|
||||
onToggleInspector={onToggleInspector}
|
||||
onNavigateWikilink={onNavigateWikilink}
|
||||
|
||||
@@ -25,7 +25,6 @@ interface EditorRightPanelProps {
|
||||
noteListFilter?: { type: string | null; query: string }
|
||||
onToggleInspector: () => void
|
||||
onToggleAIChat?: () => void
|
||||
onCopyMcpConfig?: () => void
|
||||
onNavigateWikilink: (target: string) => void
|
||||
onViewCommitDiff: (commitHash: string) => Promise<void>
|
||||
onUpdateFrontmatter?: (path: string, key: string, value: FrontmatterValue) => Promise<void>
|
||||
@@ -49,7 +48,6 @@ export function EditorRightPanel({
|
||||
inspectorEntry, inspectorContent, entries, gitHistory, vaultPath,
|
||||
noteList, noteListFilter,
|
||||
onToggleInspector, onToggleAIChat, onNavigateWikilink, onViewCommitDiff,
|
||||
onCopyMcpConfig,
|
||||
onUpdateFrontmatter, onDeleteProperty, onAddProperty, onCreateMissingType, onCreateAndOpenNote, onInitializeProperties, onToggleRawEditor, onOpenNote,
|
||||
onFileCreated, onFileModified, onVaultChanged,
|
||||
locale,
|
||||
@@ -90,7 +88,6 @@ export function EditorRightPanel({
|
||||
<AiPanelView
|
||||
controller={aiPanelController}
|
||||
onClose={() => onToggleAIChat?.()}
|
||||
onCopyMcpConfig={onCopyMcpConfig}
|
||||
onOpenNote={onOpenNote}
|
||||
onUnsupportedAiPaste={onUnsupportedAiPaste}
|
||||
defaultAiAgent={defaultAiAgent}
|
||||
|
||||
@@ -458,6 +458,23 @@ describe('SettingsPanel', () => {
|
||||
expect(screen.getByText(/to open settings/)).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('copies the MCP config from the AI Agents section', () => {
|
||||
const onCopyMcpConfig = vi.fn()
|
||||
render(
|
||||
<SettingsPanel
|
||||
open={true}
|
||||
settings={emptySettings}
|
||||
onSave={onSave}
|
||||
onCopyMcpConfig={onCopyMcpConfig}
|
||||
onClose={onClose}
|
||||
/>
|
||||
)
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: 'Copy MCP config' }))
|
||||
|
||||
expect(onCopyMcpConfig).toHaveBeenCalledOnce()
|
||||
})
|
||||
|
||||
describe('Privacy & Telemetry section', () => {
|
||||
it('renders crash reporting and analytics toggles', () => {
|
||||
render(
|
||||
|
||||
@@ -16,6 +16,7 @@ import {
|
||||
type ReactNode,
|
||||
} from 'react'
|
||||
import { Moon, Sun, X } from '@phosphor-icons/react'
|
||||
import { Copy } from 'lucide-react'
|
||||
import type { Settings } from '../types'
|
||||
import {
|
||||
APP_LOCALES,
|
||||
@@ -56,6 +57,7 @@ interface SettingsPanelProps {
|
||||
locale?: AppLocale
|
||||
systemLocale?: AppLocale
|
||||
onSave: (settings: Settings) => void
|
||||
onCopyMcpConfig?: () => void
|
||||
isGitVault?: boolean
|
||||
explicitOrganizationEnabled?: boolean
|
||||
onSaveExplicitOrganization?: (enabled: boolean) => void
|
||||
@@ -95,6 +97,7 @@ interface SettingsBodyProps {
|
||||
aiAgentsStatus: AiAgentsStatus
|
||||
defaultAiAgent: AiAgentId
|
||||
setDefaultAiAgent: (value: AiAgentId) => void
|
||||
onCopyMcpConfig?: () => void
|
||||
releaseChannel: ReleaseChannel
|
||||
setReleaseChannel: (value: ReleaseChannel) => void
|
||||
themeMode: ThemeMode
|
||||
@@ -217,6 +220,7 @@ export function SettingsPanel({
|
||||
locale = 'en',
|
||||
systemLocale = locale,
|
||||
onSave,
|
||||
onCopyMcpConfig,
|
||||
isGitVault = true,
|
||||
explicitOrganizationEnabled = true,
|
||||
onSaveExplicitOrganization,
|
||||
@@ -231,6 +235,7 @@ export function SettingsPanel({
|
||||
locale={locale}
|
||||
systemLocale={systemLocale}
|
||||
onSave={onSave}
|
||||
onCopyMcpConfig={onCopyMcpConfig}
|
||||
isGitVault={isGitVault}
|
||||
explicitOrganizationEnabled={explicitOrganizationEnabled}
|
||||
onSaveExplicitOrganization={onSaveExplicitOrganization}
|
||||
@@ -252,6 +257,7 @@ function SettingsPanelInner({
|
||||
aiAgentsStatus,
|
||||
systemLocale,
|
||||
onSave,
|
||||
onCopyMcpConfig,
|
||||
isGitVault,
|
||||
explicitOrganizationEnabled,
|
||||
onSaveExplicitOrganization,
|
||||
@@ -299,12 +305,9 @@ function SettingsPanelInner({
|
||||
onClose()
|
||||
}, [draft, onClose, onSave, onSaveExplicitOrganization, settings])
|
||||
|
||||
const handleBackdropClick = useCallback(
|
||||
(event: ReactMouseEvent<HTMLDivElement>) => {
|
||||
if (event.target === event.currentTarget) onClose()
|
||||
},
|
||||
[onClose],
|
||||
)
|
||||
const handleBackdropClick = useCallback((event: ReactMouseEvent<HTMLDivElement>) => {
|
||||
if (event.target === event.currentTarget) onClose()
|
||||
}, [onClose])
|
||||
|
||||
const handleKeyDown = useCallback(
|
||||
(event: ReactKeyboardEvent) => {
|
||||
@@ -354,6 +357,7 @@ function SettingsPanelInner({
|
||||
aiAgentsStatus={aiAgentsStatus}
|
||||
defaultAiAgent={draft.defaultAiAgent}
|
||||
setDefaultAiAgent={(value) => updateDraft('defaultAiAgent', value)}
|
||||
onCopyMcpConfig={onCopyMcpConfig}
|
||||
releaseChannel={draft.releaseChannel}
|
||||
setReleaseChannel={(value) => updateDraft('releaseChannel', value)}
|
||||
themeMode={draft.themeMode}
|
||||
@@ -415,6 +419,7 @@ function SettingsBody({
|
||||
aiAgentsStatus,
|
||||
defaultAiAgent,
|
||||
setDefaultAiAgent,
|
||||
onCopyMcpConfig,
|
||||
releaseChannel,
|
||||
setReleaseChannel,
|
||||
themeMode,
|
||||
@@ -497,6 +502,7 @@ function SettingsBody({
|
||||
aiAgentsStatus={aiAgentsStatus}
|
||||
defaultAiAgent={defaultAiAgent}
|
||||
setDefaultAiAgent={setDefaultAiAgent}
|
||||
onCopyMcpConfig={onCopyMcpConfig}
|
||||
/>
|
||||
</SettingsSection>
|
||||
|
||||
@@ -810,7 +816,8 @@ function AiAgentSettingsSection({
|
||||
aiAgentsStatus,
|
||||
defaultAiAgent,
|
||||
setDefaultAiAgent,
|
||||
}: Pick<SettingsBodyProps, 't' | 'aiAgentsStatus' | 'defaultAiAgent' | 'setDefaultAiAgent'>) {
|
||||
onCopyMcpConfig,
|
||||
}: Pick<SettingsBodyProps, 't' | 'aiAgentsStatus' | 'defaultAiAgent' | 'setDefaultAiAgent' | 'onCopyMcpConfig'>) {
|
||||
return (
|
||||
<>
|
||||
<SectionHeading
|
||||
@@ -829,6 +836,21 @@ function AiAgentSettingsSection({
|
||||
<div style={{ fontSize: 11, color: 'var(--muted-foreground)', lineHeight: 1.5 }}>
|
||||
{renderDefaultAiAgentSummary(defaultAiAgent, aiAgentsStatus, t)}
|
||||
</div>
|
||||
|
||||
{onCopyMcpConfig ? (
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={onCopyMcpConfig}
|
||||
className="w-fit gap-2"
|
||||
aria-label={t('ai.panel.copyMcpConfig')}
|
||||
data-testid="settings-copy-mcp-config"
|
||||
>
|
||||
<Copy size={15} />
|
||||
{t('ai.panel.copyMcpConfig')}
|
||||
</Button>
|
||||
) : null}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -8,21 +8,12 @@ test.describe('MCP config copy', () => {
|
||||
await expect(page.getByTestId('note-list-container')).toBeVisible({ timeout: 5_000 })
|
||||
})
|
||||
|
||||
test('copies the active-vault MCP config from the AI panel using only the keyboard', async ({ context, page }) => {
|
||||
test('copies the active-vault MCP config from the Settings AI section', async ({ context, page }) => {
|
||||
await context.grantPermissions(['clipboard-read', 'clipboard-write'])
|
||||
|
||||
await page.locator('.app__note-list .cursor-pointer').first().click()
|
||||
await page.locator('.bn-editor').click()
|
||||
await sendShortcut(page, 'L', ['Meta', 'Shift'])
|
||||
await expect(page.getByTestId('ai-panel')).toBeVisible({ timeout: 3_000 })
|
||||
|
||||
await page.getByTestId('agent-input').focus()
|
||||
await page.keyboard.press('Shift+Tab')
|
||||
await page.keyboard.press('Shift+Tab')
|
||||
await page.keyboard.press('Shift+Tab')
|
||||
const copyButton = page.getByRole('button', { name: 'Copy MCP config' })
|
||||
await expect(copyButton).toBeFocused()
|
||||
await copyButton.press('Enter')
|
||||
await sendShortcut(page, ',', ['Control'])
|
||||
await expect(page.getByTestId('settings-panel')).toBeVisible({ timeout: 3_000 })
|
||||
await page.getByTestId('settings-copy-mcp-config').click()
|
||||
|
||||
await expect.poll(() => page.evaluate(() => navigator.clipboard.readText())).toContain('"mcpServers"')
|
||||
const copiedConfig = await page.evaluate(() => navigator.clipboard.readText())
|
||||
|
||||
Reference in New Issue
Block a user