fix: unify capability settings navigation
Skills and MCP previously occupied separate top-level settings categories. They now share one Capabilities and tools category with accessible horizontal tabs, while the unimplemented tool environment remains hidden until its settings are usable. Prevent nested PageTabs wrappers from collapsing to zero height in scrollable Flex layouts, add a CSS regression guard, and document the shared rule plus the planned local Node.js and Python environment. Release note: 设置中心将 Skills 和 MCP 合并到“能力与工具”,并修复水平页签可能被压缩而不可见的问题;尚未实现的工具执行环境暂不显示。
This commit is contained in:
@@ -0,0 +1,72 @@
|
||||
import { useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { McpSettingsSection } from './McpSettingsSection'
|
||||
import { SettingsCategoryHeader } from './SettingsPrimitives'
|
||||
import { SkillsSettingsSection } from './SkillsSettingsSection'
|
||||
import { PageTabs } from './WorkspacePrimitives'
|
||||
|
||||
type CapabilitySettingsTab = 'skills' | 'mcp'
|
||||
|
||||
export function CapabilitiesAndToolsSettingsSection({
|
||||
magicNotesEnabled
|
||||
}: {
|
||||
magicNotesEnabled: boolean
|
||||
}): React.JSX.Element {
|
||||
const { t } = useTranslation('settings')
|
||||
const [activeTab, setActiveTab] =
|
||||
useState<CapabilitySettingsTab>('skills')
|
||||
const [mcpVisited, setMcpVisited] = useState(false)
|
||||
|
||||
return (
|
||||
<>
|
||||
<SettingsCategoryHeader
|
||||
category="capabilities"
|
||||
headingId="capabilities-settings-heading"
|
||||
/>
|
||||
<div className="capabilities-settings__tabs">
|
||||
<PageTabs
|
||||
ariaLabel={t('capabilities.tabs.ariaLabel')}
|
||||
idPrefix="capabilities-settings"
|
||||
onChange={(tab) => {
|
||||
setActiveTab(tab)
|
||||
if (tab === 'mcp') {
|
||||
setMcpVisited(true)
|
||||
}
|
||||
}}
|
||||
tabs={[
|
||||
{
|
||||
id: 'skills',
|
||||
label: t('capabilities.tabs.skills')
|
||||
},
|
||||
{
|
||||
id: 'mcp',
|
||||
label: t('capabilities.tabs.mcp')
|
||||
}
|
||||
]}
|
||||
value={activeTab}
|
||||
variant="segmented"
|
||||
/>
|
||||
</div>
|
||||
<section
|
||||
aria-labelledby="capabilities-settings-tab-skills"
|
||||
className="capabilities-settings__panel"
|
||||
hidden={activeTab !== 'skills'}
|
||||
id="capabilities-settings-panel-skills"
|
||||
role="tabpanel"
|
||||
>
|
||||
<SkillsSettingsSection />
|
||||
</section>
|
||||
<section
|
||||
aria-labelledby="capabilities-settings-tab-mcp"
|
||||
className="capabilities-settings__panel"
|
||||
hidden={activeTab !== 'mcp'}
|
||||
id="capabilities-settings-panel-mcp"
|
||||
role="tabpanel"
|
||||
>
|
||||
{mcpVisited && (
|
||||
<McpSettingsSection magicNotesEnabled={magicNotesEnabled} />
|
||||
)}
|
||||
</section>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -32,7 +32,7 @@ import type {
|
||||
} from '../../shared/capability-contracts'
|
||||
import { trapTabFocus } from './dialog-focus'
|
||||
import {
|
||||
SettingsCategoryHeader,
|
||||
SettingsSectionHeader,
|
||||
SettingsWarningList
|
||||
} from './SettingsPrimitives'
|
||||
import { PageTabs } from './WorkspacePrimitives'
|
||||
@@ -394,7 +394,7 @@ export function McpSettingsSection({
|
||||
|
||||
return (
|
||||
<>
|
||||
<SettingsCategoryHeader
|
||||
<SettingsSectionHeader
|
||||
actions={
|
||||
activeTab === 'custom' ? (
|
||||
<button
|
||||
@@ -410,9 +410,11 @@ export function McpSettingsSection({
|
||||
</button>
|
||||
) : undefined
|
||||
}
|
||||
category="mcp"
|
||||
description={t('mcp.description')}
|
||||
error={!editor ? error : undefined}
|
||||
headingLevel={3}
|
||||
headingId="mcp-settings-heading"
|
||||
title={t('mcp.title')}
|
||||
/>
|
||||
<SettingsWarningList warnings={snapshot?.warnings} />
|
||||
<div className="mcp-settings__tabs">
|
||||
|
||||
@@ -53,6 +53,17 @@ import type { BrandingPreferences } from './branding'
|
||||
const modelProfileId = '00000000-0000-4000-8000-000000000001'
|
||||
const nativeImage = globalThis.Image
|
||||
|
||||
function openCapabilitySettingsTab(tab: 'Skills' | 'MCP'): void {
|
||||
fireEvent.click(
|
||||
screen.getByRole('tab', { name: '能力与工具' })
|
||||
)
|
||||
fireEvent.click(
|
||||
within(
|
||||
screen.getByRole('tablist', { name: '能力与工具设置' })
|
||||
).getByRole('tab', { name: tab })
|
||||
)
|
||||
}
|
||||
|
||||
class DecodableBrandImage {
|
||||
naturalWidth = 64
|
||||
naturalHeight = 64
|
||||
@@ -1619,7 +1630,7 @@ describe('SettingsPanel runtime files', () => {
|
||||
<Harness />
|
||||
)
|
||||
|
||||
fireEvent.click(screen.getByRole('tab', { name: 'MCP' }))
|
||||
openCapabilitySettingsTab('MCP')
|
||||
const noteServerToggle = await screen.findByRole('button', {
|
||||
name: '展开服务器 笔记'
|
||||
})
|
||||
@@ -1642,7 +1653,7 @@ describe('SettingsPanel runtime files', () => {
|
||||
})
|
||||
)
|
||||
|
||||
fireEvent.click(screen.getByRole('tab', { name: 'MCP' }))
|
||||
openCapabilitySettingsTab('MCP')
|
||||
await waitFor(() =>
|
||||
expect(
|
||||
screen
|
||||
@@ -4802,7 +4813,7 @@ describe('SettingsPanel runtime files', () => {
|
||||
expect(screen.queryByText('智能心跳')).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('shows Skills and MCP as first-class settings tabs', async () => {
|
||||
it('groups Skills and MCP under Capabilities and tools', async () => {
|
||||
render(
|
||||
<SettingsPanel
|
||||
{...heartbeatSettingsProps}
|
||||
@@ -4813,7 +4824,30 @@ describe('SettingsPanel runtime files', () => {
|
||||
/>
|
||||
)
|
||||
|
||||
fireEvent.click(screen.getByRole('tab', { name: 'Skills' }))
|
||||
const categoryTabs = screen.getByRole('tablist', {
|
||||
name: '设置分类'
|
||||
})
|
||||
expect(
|
||||
within(categoryTabs).queryByRole('tab', { name: 'Skills' })
|
||||
).not.toBeInTheDocument()
|
||||
expect(
|
||||
within(categoryTabs).queryByRole('tab', { name: 'MCP' })
|
||||
).not.toBeInTheDocument()
|
||||
|
||||
fireEvent.click(
|
||||
within(categoryTabs).getByRole('tab', { name: '能力与工具' })
|
||||
)
|
||||
const capabilityTabs = screen.getByRole('tablist', {
|
||||
name: '能力与工具设置'
|
||||
})
|
||||
expect(
|
||||
within(capabilityTabs)
|
||||
.getAllByRole('tab')
|
||||
.map((tab) => tab.textContent)
|
||||
).toEqual(['Skills', 'MCP'])
|
||||
expect(
|
||||
within(capabilityTabs).getByRole('tab', { name: 'Skills' })
|
||||
).toHaveAttribute('aria-selected', 'true')
|
||||
expect(await screen.findByText('文档写作')).toBeInTheDocument()
|
||||
expect(
|
||||
screen.getByText(
|
||||
@@ -4848,7 +4882,15 @@ describe('SettingsPanel runtime files', () => {
|
||||
)
|
||||
)
|
||||
|
||||
fireEvent.click(screen.getByRole('tab', { name: 'MCP' }))
|
||||
expect(
|
||||
within(capabilityTabs).queryByRole('tab', {
|
||||
name: '工具执行环境'
|
||||
})
|
||||
).not.toBeInTheDocument()
|
||||
|
||||
fireEvent.click(
|
||||
within(capabilityTabs).getByRole('tab', { name: 'MCP' })
|
||||
)
|
||||
const mcpTabs = screen.getByRole('tablist', {
|
||||
name: 'MCP 设置分类'
|
||||
})
|
||||
@@ -5160,7 +5202,7 @@ describe('SettingsPanel runtime files', () => {
|
||||
/>
|
||||
)
|
||||
|
||||
fireEvent.click(screen.getByRole('tab', { name: 'MCP' }))
|
||||
openCapabilitySettingsTab('MCP')
|
||||
fireEvent.click(screen.getByRole('tab', { name: '自定义 MCP' }))
|
||||
const addServer = await screen.findByRole('button', {
|
||||
name: '添加 Server'
|
||||
@@ -5235,22 +5277,24 @@ describe('SettingsPanel runtime files', () => {
|
||||
})
|
||||
|
||||
it('opens an existing MCP Server in the same modal and returns focus on Escape', async () => {
|
||||
getCapabilitySnapshot.mockResolvedValueOnce({
|
||||
...capabilitySnapshot,
|
||||
mcpServers: [
|
||||
{
|
||||
id: '00000000-0000-4000-8000-000000000301',
|
||||
name: '团队知识服务',
|
||||
description: '公司内部 MCP',
|
||||
enabled: true,
|
||||
allowDynamicTools: true,
|
||||
assignments: ['model'],
|
||||
secretConfigured: true,
|
||||
transport: 'http',
|
||||
url: 'https://mcp.example.com/mcp'
|
||||
}
|
||||
]
|
||||
})
|
||||
getCapabilitySnapshot
|
||||
.mockResolvedValueOnce(capabilitySnapshot)
|
||||
.mockResolvedValueOnce({
|
||||
...capabilitySnapshot,
|
||||
mcpServers: [
|
||||
{
|
||||
id: '00000000-0000-4000-8000-000000000301',
|
||||
name: '团队知识服务',
|
||||
description: '公司内部 MCP',
|
||||
enabled: true,
|
||||
allowDynamicTools: true,
|
||||
assignments: ['model'],
|
||||
secretConfigured: true,
|
||||
transport: 'http',
|
||||
url: 'https://mcp.example.com/mcp'
|
||||
}
|
||||
]
|
||||
})
|
||||
render(
|
||||
<SettingsPanel
|
||||
{...heartbeatSettingsProps}
|
||||
@@ -5261,7 +5305,7 @@ describe('SettingsPanel runtime files', () => {
|
||||
/>
|
||||
)
|
||||
|
||||
fireEvent.click(screen.getByRole('tab', { name: 'MCP' }))
|
||||
openCapabilitySettingsTab('MCP')
|
||||
fireEvent.click(screen.getByRole('tab', { name: '自定义 MCP' }))
|
||||
const editButton = await screen.findByRole('button', {
|
||||
name: '编辑 团队知识服务'
|
||||
@@ -5287,22 +5331,24 @@ describe('SettingsPanel runtime files', () => {
|
||||
})
|
||||
|
||||
it('shows custom MCP tools under their expandable server after testing', async () => {
|
||||
getCapabilitySnapshot.mockResolvedValueOnce({
|
||||
...capabilitySnapshot,
|
||||
mcpServers: [
|
||||
{
|
||||
id: '00000000-0000-4000-8000-000000000302',
|
||||
name: '团队工具服务',
|
||||
description: '公司内部工具',
|
||||
enabled: true,
|
||||
allowDynamicTools: true,
|
||||
assignments: ['model'],
|
||||
secretConfigured: false,
|
||||
transport: 'http',
|
||||
url: 'https://mcp.example.com/mcp'
|
||||
}
|
||||
]
|
||||
})
|
||||
getCapabilitySnapshot
|
||||
.mockResolvedValueOnce(capabilitySnapshot)
|
||||
.mockResolvedValueOnce({
|
||||
...capabilitySnapshot,
|
||||
mcpServers: [
|
||||
{
|
||||
id: '00000000-0000-4000-8000-000000000302',
|
||||
name: '团队工具服务',
|
||||
description: '公司内部工具',
|
||||
enabled: true,
|
||||
allowDynamicTools: true,
|
||||
assignments: ['model'],
|
||||
secretConfigured: false,
|
||||
transport: 'http',
|
||||
url: 'https://mcp.example.com/mcp'
|
||||
}
|
||||
]
|
||||
})
|
||||
vi.mocked(
|
||||
window.goodbuddy.capabilities.testMcpServer
|
||||
).mockResolvedValueOnce({
|
||||
@@ -5352,7 +5398,7 @@ describe('SettingsPanel runtime files', () => {
|
||||
/>
|
||||
)
|
||||
|
||||
fireEvent.click(screen.getByRole('tab', { name: 'MCP' }))
|
||||
openCapabilitySettingsTab('MCP')
|
||||
fireEvent.click(screen.getByRole('tab', { name: '自定义 MCP' }))
|
||||
const serverToggle = await screen.findByRole('button', {
|
||||
name: '展开服务器 团队工具服务'
|
||||
|
||||
@@ -41,9 +41,8 @@ import {
|
||||
isAgentRuntimeModelProtocol,
|
||||
isDeepSeekHarnessModelProfile
|
||||
} from '../../shared/contracts'
|
||||
import { McpSettingsSection } from './McpSettingsSection'
|
||||
import { RolePromptSettingsSection } from './RolePromptSettingsSection'
|
||||
import { SkillsSettingsSection } from './SkillsSettingsSection'
|
||||
import { CapabilitiesAndToolsSettingsSection } from './CapabilitiesAndToolsSettingsSection'
|
||||
import { ChannelSettingsSection } from './ChannelSettingsSection'
|
||||
import { UpdateSettingsSection } from './UpdateSettingsSection'
|
||||
import { PlatformFeaturesSettingsSection } from './PlatformFeaturesSettingsSection'
|
||||
@@ -842,8 +841,7 @@ export function SettingsPanel({
|
||||
activeTab === 'document-parsing' ||
|
||||
activeTab === 'ssh-hosts' ||
|
||||
activeTab === 'channels' ||
|
||||
activeTab === 'skills' ||
|
||||
activeTab === 'mcp' ||
|
||||
activeTab === 'capabilities' ||
|
||||
activeTab === 'about'
|
||||
|
||||
const savedConfiguredSettings = settings
|
||||
@@ -3930,9 +3928,8 @@ export function SettingsPanel({
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
{activeTab === 'skills' && <SkillsSettingsSection />}
|
||||
{activeTab === 'mcp' && (
|
||||
<McpSettingsSection
|
||||
{activeTab === 'capabilities' && (
|
||||
<CapabilitiesAndToolsSettingsSection
|
||||
magicNotesEnabled={magicNotesEnabled}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -46,11 +46,38 @@ export function SettingsCategoryHeader({
|
||||
headingId?: string
|
||||
}): React.JSX.Element {
|
||||
const definition = settingsCategories[category]
|
||||
return (
|
||||
<SettingsSectionHeader
|
||||
actions={actions}
|
||||
description={definition.description}
|
||||
error={error}
|
||||
headingId={headingId}
|
||||
title={definition.label}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export function SettingsSectionHeader({
|
||||
actions,
|
||||
description,
|
||||
error,
|
||||
headingLevel = 2,
|
||||
headingId,
|
||||
title
|
||||
}: {
|
||||
actions?: ReactNode
|
||||
description: string
|
||||
error?: string
|
||||
headingLevel?: 2 | 3
|
||||
headingId: string
|
||||
title: string
|
||||
}): React.JSX.Element {
|
||||
const Heading = headingLevel === 3 ? 'h3' : 'h2'
|
||||
return (
|
||||
<header className="settings-category-header">
|
||||
<div className="settings-category-header__content">
|
||||
<h2 id={headingId}>{definition.label}</h2>
|
||||
<p>{definition.description}</p>
|
||||
<Heading id={headingId}>{title}</Heading>
|
||||
<p>{description}</p>
|
||||
</div>
|
||||
{actions && (
|
||||
<div className="settings-category-header__actions">
|
||||
|
||||
@@ -6,7 +6,7 @@ import type {
|
||||
CapabilitySnapshot,
|
||||
RuntimeTarget
|
||||
} from '../../shared/capability-contracts'
|
||||
import { SettingsCategoryHeader } from './SettingsPrimitives'
|
||||
import { SettingsSectionHeader } from './SettingsPrimitives'
|
||||
|
||||
export function SkillsSettingsSection(): React.JSX.Element {
|
||||
const { t } = useTranslation('settingsSections')
|
||||
@@ -62,7 +62,7 @@ export function SkillsSettingsSection(): React.JSX.Element {
|
||||
|
||||
return (
|
||||
<>
|
||||
<SettingsCategoryHeader
|
||||
<SettingsSectionHeader
|
||||
actions={
|
||||
<>
|
||||
<button
|
||||
@@ -93,9 +93,11 @@ export function SkillsSettingsSection(): React.JSX.Element {
|
||||
</button>
|
||||
</>
|
||||
}
|
||||
category="skills"
|
||||
description={t('skills.description')}
|
||||
error={error}
|
||||
headingLevel={3}
|
||||
headingId="skills-settings-heading"
|
||||
title={t('skills.title')}
|
||||
/>
|
||||
<section
|
||||
aria-label={t('skills.listLabel')}
|
||||
|
||||
@@ -100,6 +100,15 @@ describe('WorkspacePrimitives', () => {
|
||||
expect(platformFeaturesTabs).toMatch(/flex:\s*0 0 auto;/u)
|
||||
})
|
||||
|
||||
it('keeps Capabilities and tools tabs visible in scrollable Settings', () => {
|
||||
const capabilitiesTabs = stylesheet.match(
|
||||
/\.capabilities-settings__tabs\s*\{(?<rules>[^}]*)\}/u
|
||||
)?.groups?.rules
|
||||
|
||||
expect(capabilitiesTabs).toBeDefined()
|
||||
expect(capabilitiesTabs).toMatch(/flex:\s*0 0 auto;/u)
|
||||
})
|
||||
|
||||
it('keeps whole-surface dialog scrollbars outside close-button hitboxes', () => {
|
||||
expect(stylesheet).toMatch(
|
||||
/:is\(\s*\.project-create-card,\s*\.knowledge-citation-dialog__surface,\s*\.channel-qr-dialog,\s*\.document-parsing-diagnostic,\s*\.mcp-editor\s*\)\s*\{[^}]*scrollbar-gutter:\s*stable;/u
|
||||
|
||||
@@ -145,6 +145,9 @@ export const integrations = {
|
||||
save: 'Save channel settings'
|
||||
},
|
||||
mcp: {
|
||||
title: 'MCP',
|
||||
description:
|
||||
'View built-in tools and MCP servers, and manage external MCP servers',
|
||||
runtimeLabels: {
|
||||
model: 'Model',
|
||||
opencode: 'OpenCode',
|
||||
|
||||
@@ -73,17 +73,10 @@ export const settings = {
|
||||
navigationDescription: 'Roles, descriptions, and system prompts',
|
||||
description: 'Roles, descriptions, and system prompts'
|
||||
},
|
||||
skills: {
|
||||
label: 'Skills',
|
||||
navigationDescription: 'Built-in and custom capabilities',
|
||||
description:
|
||||
'Works with direct models, OpenCode, Continue, and DeepSeek Harness'
|
||||
},
|
||||
mcp: {
|
||||
label: 'MCP',
|
||||
navigationDescription: 'Tool servers and credentials',
|
||||
description:
|
||||
'View built-in tools and MCP servers, and manage external MCP servers'
|
||||
capabilities: {
|
||||
label: 'Capabilities and tools',
|
||||
navigationDescription: 'Skills and MCP',
|
||||
description: 'Manage Skills and MCP'
|
||||
},
|
||||
about: {
|
||||
label: 'About and updates',
|
||||
@@ -92,6 +85,13 @@ export const settings = {
|
||||
'Checks the selected official update source and never installs automatically'
|
||||
}
|
||||
},
|
||||
capabilities: {
|
||||
tabs: {
|
||||
ariaLabel: 'Capabilities and tools settings',
|
||||
skills: 'Skills',
|
||||
mcp: 'MCP'
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
testing: 'Testing…',
|
||||
saveAndTestModel: 'Save and test model',
|
||||
|
||||
@@ -698,6 +698,9 @@ export const settingsSections = {
|
||||
}
|
||||
},
|
||||
skills: {
|
||||
title: 'Skills',
|
||||
description:
|
||||
'Works with direct models, OpenCode, Continue, and DeepSeek Harness',
|
||||
runtimeLabels: {
|
||||
model: 'Model',
|
||||
opencode: 'OpenCode',
|
||||
|
||||
@@ -134,6 +134,8 @@ export const integrations = {
|
||||
save: '保存通道设置'
|
||||
},
|
||||
mcp: {
|
||||
title: 'MCP',
|
||||
description: '查看内置工具、内置 MCP,并管理外部 MCP Server',
|
||||
runtimeLabels: {
|
||||
model: '模型',
|
||||
opencode: 'OpenCode',
|
||||
|
||||
@@ -62,15 +62,10 @@ export const settings = {
|
||||
navigationDescription: '角色、说明与系统提示词',
|
||||
description: '角色、说明与系统提示词'
|
||||
},
|
||||
skills: {
|
||||
label: 'Skills',
|
||||
navigationDescription: '内置与自定义能力',
|
||||
description: '支持直连模型、OpenCode、Continue 和 DeepSeek Harness'
|
||||
},
|
||||
mcp: {
|
||||
label: 'MCP',
|
||||
navigationDescription: '工具服务与凭据',
|
||||
description: '查看内置工具、内置 MCP,并管理外部 MCP Server'
|
||||
capabilities: {
|
||||
label: '能力与工具',
|
||||
navigationDescription: 'Skills 与 MCP',
|
||||
description: '管理 Skills 和 MCP'
|
||||
},
|
||||
about: {
|
||||
label: '关于与更新',
|
||||
@@ -78,6 +73,13 @@ export const settings = {
|
||||
description: '检查所选官方更新源,不自动下载安装'
|
||||
}
|
||||
},
|
||||
capabilities: {
|
||||
tabs: {
|
||||
ariaLabel: '能力与工具设置',
|
||||
skills: 'Skills',
|
||||
mcp: 'MCP'
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
testing: '测试中…',
|
||||
saveAndTestModel: '保存并测试模型',
|
||||
|
||||
@@ -652,6 +652,8 @@ export const settingsSections = {
|
||||
}
|
||||
},
|
||||
skills: {
|
||||
title: 'Skills',
|
||||
description: '支持直连模型、OpenCode、Continue 和 DeepSeek Harness',
|
||||
runtimeLabels: {
|
||||
model: '模型',
|
||||
opencode: 'OpenCode',
|
||||
|
||||
@@ -42,12 +42,8 @@ export const settingsCategoryList = [
|
||||
translationKey: 'roles'
|
||||
},
|
||||
{
|
||||
id: 'skills',
|
||||
translationKey: 'skills'
|
||||
},
|
||||
{
|
||||
id: 'mcp',
|
||||
translationKey: 'mcp'
|
||||
id: 'capabilities',
|
||||
translationKey: 'capabilities'
|
||||
},
|
||||
{
|
||||
id: 'about',
|
||||
|
||||
@@ -7615,7 +7615,7 @@ button > svg {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.settings-category-header__content h2 {
|
||||
.settings-category-header__content :is(h2, h3) {
|
||||
margin: 0;
|
||||
color: var(--text-primary);
|
||||
font-size: var(--font-section-title);
|
||||
@@ -10432,6 +10432,27 @@ details.settings-section > :not(summary) + :not(summary) {
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.capabilities-settings__tabs {
|
||||
max-width: 100%;
|
||||
overflow-x: auto;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.capabilities-settings__tabs > .page-tabs {
|
||||
min-width: max-content;
|
||||
}
|
||||
|
||||
.capabilities-settings__panel {
|
||||
display: flex;
|
||||
min-width: 0;
|
||||
flex-direction: column;
|
||||
gap: var(--space-4);
|
||||
}
|
||||
|
||||
.capabilities-settings__panel[hidden] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.mcp-tool-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
Reference in New Issue
Block a user