fix: hide inactive browser profile settings

Managed browser profile controls suggested that login state and cookies would be reused, but named profiles do not yet affect browser execution. The settings UI now hides profile management while preserving the built-in browser switch, diagnostics, stored profile metadata, and compatibility APIs.

Release note: 暂时隐藏尚未实际生效的托管浏览器配置,避免误以为登录状态和 Cookie 会被复用;已有底层数据不会删除。
This commit is contained in:
mesalogo
2026-08-18 12:36:18 +08:00
parent 7a46422fc1
commit 08884c127c
9 changed files with 20 additions and 348 deletions
+1 -1
View File
@@ -539,7 +539,7 @@ GoodBuddy 是可调整窗口大小的桌面应用。响应式设计优先保证
- Agent Runtime 页面在低层程序与配置覆盖之外提供“能力与默认配置”区域。能力清单使用共享 `PageTabs`,按 Agents、Tools、Commands、Skills、MCP、Rules、Prompts、Resources、LSP、Formatters 和上下文 11 类单行滚动展示,一次只呈现当前分类的 `tabpanel`;清单只显示 Runtime 自有能力,不混入 GoodBuddy 分配的 Skills、临时 MCP 或 Continue 预设。Tools 必须独立于 Commands、LSP 和 Formatters,显示工具类型、来源及 Ask/Execute 可用性;清单状态必须区分完整、部分、不可用、仅连接和不支持,不能用进程连通性冒充清单可读。 - Agent Runtime 页面在低层程序与配置覆盖之外提供“能力与默认配置”区域。能力清单使用共享 `PageTabs`,按 Agents、Tools、Commands、Skills、MCP、Rules、Prompts、Resources、LSP、Formatters 和上下文 11 类单行滚动展示,一次只呈现当前分类的 `tabpanel`;清单只显示 Runtime 自有能力,不混入 GoodBuddy 分配的 Skills、临时 MCP 或 Continue 预设。Tools 必须独立于 Commands、LSP 和 Formatters,显示工具类型、来源及 Ask/Execute 可用性;清单状态必须区分完整、部分、不可用、仅连接和不支持,不能用进程连通性冒充清单可读。
- “能力与默认配置”只显示一个模块标题,刷新入口位于该标题右侧,能力状态压缩为一行并排在默认 Agent 或 Continue 预设编辑器之前;不得再复制“Runtime 原生能力”等同义标题、说明或状态结论。刷新只更新能力快照,不覆盖未保存的原生定制草稿。 - “能力与默认配置”只显示一个模块标题,刷新入口位于该标题右侧,能力状态压缩为一行并排在默认 Agent 或 Continue 预设编辑器之前;不得再复制“Runtime 原生能力”等同义标题、说明或状态结论。刷新只更新能力快照,不覆盖未保存的原生定制草稿。
- OpenCode 的默认 Agent 使用原生下拉选择;Continue 预设编辑器允许管理名称、说明、启用的 Rules 以及 Prompt 名称、说明和正文,并可展开查看原生 Rules 与启用预设 Rules 的最终合并顺序。持久启停仍使用共享 Switch,添加与删除使用明确按钮和可访问名称。 - OpenCode 的默认 Agent 使用原生下拉选择;Continue 预设编辑器允许管理名称、说明、启用的 Rules 以及 Prompt 名称、说明和正文,并可展开查看原生 Rules 与启用预设 Rules 的最终合并顺序。持久启停仍使用共享 Switch,添加与删除使用明确按钮和可访问名称。
- MCP 设置按“内置 MCP / 直连模型 / 自定义 MCP / 电脑控制”四个同级 `PageTabs` 组织。直连模型中的联网搜索与内置浏览器使用一致的折叠卡片和独立总开关;内置浏览器必须明确说明其操作 GoodBuddy 隔离浏览器,不控制客户端已安装的浏览器,开启后可由 Execute 直接使用,不逐次询问。托管浏览器配置归入直连模型,“电脑控制”只显示实际操作客户端电脑的能力。内置 MCP 卡片与 Skills 一样提供持久启停和 Runtime 分配;直连模型、GoodBuddy 管理的 OpenCode 与 Continue 默认选中且可调整,DeepSeek Harness 必须以置灰、未选择和“暂不支持”文案持续显示,不能呈现为可保存的分配。魔法笔记 MCP 的自身启停与平台功能依赖分别显示,依赖未开启时保留用户配置并说明当前不会加载。 - MCP 设置按“内置 MCP / 直连模型 / 自定义 MCP / 电脑控制”四个同级 `PageTabs` 组织。直连模型中的联网搜索与内置浏览器使用一致的折叠卡片和独立总开关;内置浏览器必须明确说明其操作 GoodBuddy 隔离浏览器,不控制客户端已安装的浏览器,开启后可由 Execute 直接使用,不逐次询问。尚未生效的命名浏览器配置不得显示在界面中,“电脑控制”只显示实际操作客户端电脑的能力。内置 MCP 卡片与 Skills 一样提供持久启停和 Runtime 分配;直连模型、GoodBuddy 管理的 OpenCode 与 Continue 默认选中且可调整,DeepSeek Harness 必须以置灰、未选择和“暂不支持”文案持续显示,不能呈现为可保存的分配。魔法笔记 MCP 的自身启停与平台功能依赖分别显示,依赖未开启时保留用户配置并说明当前不会加载。
- MCP Server 测试结果在同一展开卡片中分组显示 Tools、Prompts 和 Resources 的支持状态、数量与有界元数据;Prompt 参数标明必填项,Resource 只显示 URI、名称、类型和说明,不读取或渲染 Resource 内容。 - MCP Server 测试结果在同一展开卡片中分组显示 Tools、Prompts 和 Resources 的支持状态、数量与有界元数据;Prompt 参数标明必填项,Resource 只显示 URI、名称、类型和说明,不读取或渲染 Resource 内容。
### 13.8 文档解析设置 ### 13.8 文档解析设置
@@ -6,7 +6,7 @@
| --- | --- | | --- | --- |
| 文档类型 | 实施进度 | | 文档类型 | 实施进度 |
| 状态 | 持续更新 | | 状态 | 持续更新 |
| 版本 | 0.2 | | 版本 | 0.3 |
| 日期 | 2026-08-18 | | 日期 | 2026-08-18 |
| 适用能力 | 内置浏览器与客户端电脑控制 | | 适用能力 | 内置浏览器与客户端电脑控制 |
@@ -63,7 +63,9 @@ Linux x64 和 Linux arm64。
- 内置浏览器已归入“直连模型”工具,与联网搜索并列,并明确说明不会控制 - 内置浏览器已归入“直连模型”工具,与联网搜索并列,并明确说明不会控制
客户端已安装的浏览器;原有开关和配置 ID 保持不变,用户状态无需迁移。 客户端已安装的浏览器;原有开关和配置 ID 保持不变,用户状态无需迁移。
- 用户通过内置浏览器总开关决定是否向直连模型提供工具;开启后可在 Execute - 用户通过内置浏览器总开关决定是否向直连模型提供工具;开启后可在 Execute
模式直接使用,不逐次询问。托管浏览器配置和诊断位于同一分类。 模式直接使用,不逐次询问。诊断位于同一分类。
- 尚未实际参与浏览器执行的命名配置已从界面隐藏;底层旧数据和兼容接口继续
保留,不删除用户已有记录。
- “电脑控制”只显示实际操作客户端电脑的能力。能力启用、配置变更和本地 - “电脑控制”只显示实际操作客户端电脑的能力。能力启用、配置变更和本地
数据清除仍会替换或清理相关 Runtime 和会话。 数据清除仍会替换或清理相关 Runtime 和会话。
@@ -135,10 +137,11 @@ Linux x64 和 Linux arm64。
区分代理、分区、窗口、CDP 初始化、DNS 或页面导航失败。需要增加有界、脱敏的 区分代理、分区、窗口、CDP 初始化、DNS 或页面导航失败。需要增加有界、脱敏的
阶段错误码,并保留用户可执行的修复建议。 阶段错误码,并保留用户可执行的修复建议。
### P1:命名浏览器配置尚未用于执行 ### P1:命名浏览器配置尚未用于执行,界面已隐藏
设置中的命名浏览器配置当前只保存未来托管隔离所需的元数据实际执行仍使用 命名浏览器配置当前只保存未来托管隔离所需的元数据实际执行仍使用每个会话
每个会话随机创建的临时分区,不复用登录状态。 随机创建的临时分区,不复用登录状态。为避免用户误以为该配置已经生效,设置
界面暂不显示创建、选择、重命名或删除入口;底层数据与兼容接口继续保留。
## 下一步顺序 ## 下一步顺序
+1 -1
View File
@@ -733,7 +733,7 @@ export class CapabilityService {
await this.browserProfiles.getSnapshot() await this.browserProfiles.getSnapshot()
return { return {
status: 'available', status: 'available',
summary: '隔离的托管浏览器配置存储可用。' summary: '内置浏览器隔离存储可用。'
} }
} }
} }
@@ -36,7 +36,7 @@ export const computerCapabilityCatalog: readonly ComputerCapabilityCatalogEntry[
id: 'host-browser-control', id: 'host-browser-control',
name: '内置浏览器', name: '内置浏览器',
description: description:
'使用 GoodBuddy 内置的临时隔离浏览器执行网页操作,不会控制客户端已安装的浏览器;命名配置当前仅保存未来托管隔离所需的元数据。', '使用 GoodBuddy 内置的临时隔离浏览器执行网页操作,不会控制客户端已安装的浏览器。',
enabledByDefault: false, enabledByDefault: false,
implementationKind: 'managed-browser-driver', implementationKind: 'managed-browser-driver',
supportedTargets: Object.freeze([ supportedTargets: Object.freeze([
-204
View File
@@ -3,7 +3,6 @@ import {
CircleAlert, CircleAlert,
Database, Database,
FlaskConical, FlaskConical,
Globe2,
MonitorCog, MonitorCog,
Network, Network,
Pencil, Pencil,
@@ -136,10 +135,6 @@ export function McpSettingsSection({
const [diagnostics, setDiagnostics] = useState< const [diagnostics, setDiagnostics] = useState<
Partial<Record<ComputerCapabilityId, CapabilityDiagnosticReport>> Partial<Record<ComputerCapabilityId, CapabilityDiagnosticReport>>
>({}) >({})
const [newProfileName, setNewProfileName] = useState('')
const [profileNames, setProfileNames] = useState<Record<string, string>>(
{}
)
const [activeTab, setActiveTab] = const [activeTab, setActiveTab] =
useState<McpSettingsTab>('builtin') useState<McpSettingsTab>('builtin')
const editorDialogRef = useRef<HTMLDivElement>(null) const editorDialogRef = useRef<HTMLDivElement>(null)
@@ -231,21 +226,6 @@ export function McpSettingsSection({
} }
} }
const createProfile = async (): Promise<void> => {
const name = newProfileName.trim()
if (!name) {
return
}
if (
await run('profile:create', () =>
window.goodbuddy.capabilities.createBrowserProfile?.({ name }) ??
Promise.reject(new Error(t('mcp.errors.unsupportedProfiles')))
)
) {
setNewProfileName('')
}
}
const save = async (): Promise<void> => { const save = async (): Promise<void> => {
if (!editor) { if (!editor) {
return return
@@ -405,10 +385,6 @@ export function McpSettingsSection({
const builtinMcpStates = new Map( const builtinMcpStates = new Map(
snapshot?.builtinMcpServers?.map((server) => [server.id, server]) snapshot?.builtinMcpServers?.map((server) => [server.id, server])
) )
const browserProfiles = snapshot?.browserProfiles ?? {
profiles: [],
defaultProfileId: null
}
const webSearch = snapshot?.webSearch ?? { const webSearch = snapshot?.webSearch ?? {
provider: 'exa' as const, provider: 'exa' as const,
enabled: true, enabled: true,
@@ -601,154 +577,6 @@ export function McpSettingsSection({
</section> </section>
)} )}
{activeTab === 'model-tools' && browserCapability && (
<section
aria-labelledby="browser-profiles-heading"
className="mcp-tool-section"
>
<div className="mcp-subsection-heading">
<div>
<Globe2 size={15} />
<strong id="browser-profiles-heading">
{t('mcp.profiles.title')}
</strong>
</div>
<small>
{t('mcp.profiles.count', {
count: browserProfiles.profiles.length
})}
</small>
</div>
<p className="settings-notice">
{t('mcp.profiles.notice')}
</p>
<div className="browser-profile-create">
<label className="field">
<span>{t('mcp.profiles.newName')}</span>
<input
onChange={(event) => setNewProfileName(event.target.value)}
placeholder={t('mcp.profiles.placeholder')}
value={newProfileName}
/>
</label>
<button
className="secondary-button"
disabled={Boolean(busy) || !newProfileName.trim()}
onClick={() => void createProfile()}
type="button"
>
<Plus size={13} />
{t('mcp.profiles.create')}
</button>
</div>
<div className="browser-profile-list">
{browserProfiles.profiles.length === 0 && (
<p className="settings-empty">{t('mcp.profiles.empty')}</p>
)}
{browserProfiles.profiles.map((profile) => {
const referenced = computerCapabilities.some(
(capability) =>
capability.browserProfileId === profile.id
)
return (
<article className="browser-profile-row" key={profile.id}>
<label className="field">
<span>{t('mcp.profiles.name')}</span>
<input
aria-label={t('mcp.profiles.nameAriaLabel', {
name: profile.name
})}
onChange={(event) =>
setProfileNames((current) => ({
...current,
[profile.id]: event.target.value
}))
}
value={profileNames[profile.id] ?? profile.name}
/>
</label>
<label className="browser-profile-default">
<input
aria-label={t(
'mcp.profiles.setDefaultAriaLabel',
{ name: profile.name }
)}
checked={
browserProfiles.defaultProfileId === profile.id
}
disabled={Boolean(busy)}
name="default-browser-profile"
onChange={() =>
void run(`profile:default:${profile.id}`, () =>
window.goodbuddy.capabilities.setDefaultBrowserProfile?.(
profile.id
) ??
Promise.reject(
new Error(t('mcp.errors.unsupportedProfiles'))
)
)
}
type="radio"
/>
{t('mcp.profiles.default')}
</label>
<button
aria-label={t('mcp.profiles.renameAriaLabel', {
name: profile.name
})}
className="secondary-button"
disabled={
Boolean(busy) ||
!(profileNames[profile.id] ?? '').trim() ||
profileNames[profile.id] === profile.name
}
onClick={() =>
void run(`profile:rename:${profile.id}`, () =>
window.goodbuddy.capabilities.renameBrowserProfile?.({
profileId: profile.id,
name: profileNames[profile.id] ?? profile.name
}) ??
Promise.reject(
new Error(t('mcp.errors.unsupportedProfiles'))
)
)
}
type="button"
>
<Pencil size={13} />
{t('mcp.profiles.rename')}
</button>
<button
aria-label={t('mcp.profiles.deleteAriaLabel', {
name: profile.name
})}
className="danger-ghost"
disabled={Boolean(busy) || referenced}
onClick={() =>
void run(`profile:remove:${profile.id}`, () =>
window.goodbuddy.capabilities.removeBrowserProfile?.(
profile.id
) ??
Promise.reject(
new Error(t('mcp.errors.unsupportedProfiles'))
)
)
}
title={
referenced ? t('mcp.profiles.inUse') : undefined
}
type="button"
>
<Trash2 size={13} />
{t('mcp.profiles.delete')}
</button>
</article>
)
})}
</div>
</section>
)}
{activeTab === 'builtin' && ( {activeTab === 'builtin' && (
<section <section
aria-labelledby="builtin-mcp-heading" aria-labelledby="builtin-mcp-heading"
@@ -1219,38 +1047,6 @@ export function McpSettingsSection({
<CircleAlert aria-hidden="true" size={13} /> <CircleAlert aria-hidden="true" size={13} />
{t('mcp.browser.control')} {t('mcp.browser.control')}
</p> </p>
<label className="field computer-capability-profile">
<span>{t('mcp.browser.profile')}</span>
<select
aria-label={t('mcp.browser.profileAriaLabel')}
disabled={Boolean(busy)}
onChange={(event) =>
void run('browser:profile', () =>
window.goodbuddy.capabilities.setComputerCapabilityBrowserProfile?.(
browserCapability.id,
event.target.value || null
) ??
Promise.reject(
new Error(
t('mcp.errors.unsupportedProfiles')
)
)
)
}
value={
browserCapability.browserProfileId ?? ''
}
>
<option value="">
{t('mcp.browser.defaultProfile')}
</option>
{browserProfiles.profiles.map((profile) => (
<option key={profile.id} value={profile.id}>
{profile.name}
</option>
))}
</select>
</label>
<div className="capability-diagnostic"> <div className="capability-diagnostic">
<button <button
aria-label={t( aria-label={t(
+7 -33
View File
@@ -234,7 +234,7 @@ const capabilitySnapshot = {
{ {
id: 'host-browser-control' as const, id: 'host-browser-control' as const,
name: '内置浏览器', name: '内置浏览器',
description: '使用隔离的托管浏览器配置执行网页操作。', description: '使用 GoodBuddy 内置的临时隔离浏览器执行网页操作。',
enabled: false, enabled: false,
supported: true, supported: true,
browserProfileId: null, browserProfileId: null,
@@ -321,10 +321,10 @@ const diagnoseComputerCapability = vi.fn(async () => ({
checkedAt: '2026-08-05T12:00:00.000Z', checkedAt: '2026-08-05T12:00:00.000Z',
checks: [ checks: [
{ {
id: 'managed-profile-root', id: 'browser-executable',
status: 'degraded' as const, status: 'degraded' as const,
summary: '托管配置可用,但尚未选择默认网站。', summary: '内置浏览器核心可用,但当前未启动会话。',
remedy: '先创建并选择托管配置。' remedy: '开始一次浏览器任务后重试。'
} }
] ]
})) }))
@@ -3656,35 +3656,9 @@ describe('SettingsPanel runtime files', () => {
expect(diagnoseComputerCapability).toHaveBeenCalledWith( expect(diagnoseComputerCapability).toHaveBeenCalledWith(
'host-browser-control' 'host-browser-control'
) )
fireEvent.change(screen.getByLabelText('新配置名称'), { expect(screen.queryByText('托管浏览器配置')).not.toBeInTheDocument()
target: { value: '购物网站' } expect(screen.queryByText('内置浏览器配置')).not.toBeInTheDocument()
}) expect(screen.queryByLabelText('新配置名称')).not.toBeInTheDocument()
fireEvent.click(
screen.getByRole('button', { name: '创建托管配置' })
)
await waitFor(() =>
expect(createBrowserProfile).toHaveBeenCalledWith({
name: '购物网站'
})
)
fireEvent.change(screen.getByLabelText('配置名称 工作网站'), {
target: { value: '工作站点' }
})
fireEvent.click(
screen.getByRole('button', { name: '重命名配置 工作网站' })
)
await waitFor(() =>
expect(renameBrowserProfile).toHaveBeenCalledWith({
profileId: browserProfileId,
name: '工作站点'
})
)
fireEvent.click(
screen.getByRole('button', { name: '删除配置 工作网站' })
)
await waitFor(() =>
expect(removeBrowserProfile).toHaveBeenCalledWith(browserProfileId)
)
expect( expect(
screen.queryByRole('switch', { screen.queryByRole('switch', {
name: '启用 Linux 桌面控制' name: '启用 Linux 桌面控制'
@@ -164,8 +164,6 @@ export const integrations = {
unsupportedDiagnostics: unsupportedDiagnostics:
'Computer control diagnostics are unavailable in this version', 'Computer control diagnostics are unavailable in this version',
diagnostics: 'Capability diagnostics failed', diagnostics: 'Capability diagnostics failed',
unsupportedProfiles:
'Managed browser profiles are unavailable in this version',
test: 'MCP connection test failed', test: 'MCP connection test failed',
unsupportedBuiltinBrowser: unsupportedBuiltinBrowser:
'The built-in browser is unavailable in this version', 'The built-in browser is unavailable in this version',
@@ -200,23 +198,7 @@ export const integrations = {
remedy: ' Suggested action: {{remedy}}' remedy: ' Suggested action: {{remedy}}'
}, },
profiles: { profiles: {
title: 'Managed browser profiles', count: '{{count}}'
count: '{{count}}',
notice:
'Each profile uses isolated storage managed by GoodBuddy. The interface never receives or displays executable paths, command arguments, or environment variables.',
newName: 'New profile name',
placeholder: 'For example: Work sites',
create: 'Create managed profile',
empty: 'No managed browser profiles',
name: 'Profile name',
nameAriaLabel: 'Profile name {{name}}',
setDefaultAriaLabel: 'Set {{name}} as the default profile',
default: 'Default',
renameAriaLabel: 'Rename profile {{name}}',
rename: 'Rename',
deleteAriaLabel: 'Delete profile {{name}}',
delete: 'Delete',
inUse: 'This profile is used by the built-in browser'
}, },
builtin: { builtin: {
title: 'Built-in GoodBuddy MCP', title: 'Built-in GoodBuddy MCP',
@@ -266,9 +248,6 @@ export const integrations = {
enableAriaLabel: 'Enable the direct-model built-in browser', enableAriaLabel: 'Enable the direct-model built-in browser',
enabled: 'Enabled', enabled: 'Enabled',
disabled: 'Disabled', disabled: 'Disabled',
profile: 'Built-in browser profile',
profileAriaLabel: 'Managed profile used by the built-in browser',
defaultProfile: 'Use the default managed profile',
diagnoseAriaLabel: 'Diagnose the built-in browser', diagnoseAriaLabel: 'Diagnose the built-in browser',
toolsAriaLabel: 'Direct-model built-in browser tools' toolsAriaLabel: 'Direct-model built-in browser tools'
}, },
@@ -153,7 +153,6 @@ export const integrations = {
operation: '能力设置操作失败', operation: '能力设置操作失败',
unsupportedDiagnostics: '当前版本不支持电脑控制能力诊断', unsupportedDiagnostics: '当前版本不支持电脑控制能力诊断',
diagnostics: '能力诊断失败', diagnostics: '能力诊断失败',
unsupportedProfiles: '当前版本不支持托管浏览器配置',
test: 'MCP 连接测试失败', test: 'MCP 连接测试失败',
unsupportedBuiltinBrowser: '当前版本不支持内置浏览器', unsupportedBuiltinBrowser: '当前版本不支持内置浏览器',
unsupportedComputerControl: '当前版本不支持电脑控制能力' unsupportedComputerControl: '当前版本不支持电脑控制能力'
@@ -186,23 +185,7 @@ export const integrations = {
remedy: ' 处理建议:{{remedy}}' remedy: ' 处理建议:{{remedy}}'
}, },
profiles: { profiles: {
title: '托管浏览器配置', count: '{{count}} 个'
count: '{{count}} 个',
notice:
'每个配置使用 GoodBuddy 管理的隔离存储;界面不会接收或显示可执行路径、命令参数与环境变量。',
newName: '新配置名称',
placeholder: '例如:工作网站',
create: '创建托管配置',
empty: '尚未创建托管浏览器配置',
name: '配置名称',
nameAriaLabel: '配置名称 {{name}}',
setDefaultAriaLabel: '设为默认配置 {{name}}',
default: '默认',
renameAriaLabel: '重命名配置 {{name}}',
rename: '重命名',
deleteAriaLabel: '删除配置 {{name}}',
delete: '删除',
inUse: '此配置正被内置浏览器使用'
}, },
builtin: { builtin: {
title: 'GoodBuddy 内置 MCP', title: 'GoodBuddy 内置 MCP',
@@ -248,9 +231,6 @@ export const integrations = {
enableAriaLabel: '启用直连模型内置浏览器', enableAriaLabel: '启用直连模型内置浏览器',
enabled: '已启用', enabled: '已启用',
disabled: '已停用', disabled: '已停用',
profile: '内置浏览器配置',
profileAriaLabel: '内置浏览器使用的托管配置',
defaultProfile: '使用默认托管配置',
diagnoseAriaLabel: '诊断内置浏览器', diagnoseAriaLabel: '诊断内置浏览器',
toolsAriaLabel: '直连模型内置浏览器工具' toolsAriaLabel: '直连模型内置浏览器工具'
}, },
-60
View File
@@ -7145,10 +7145,6 @@ details.settings-section > :not(summary) + :not(summary) {
margin-top: 1px; margin-top: 1px;
} }
.computer-capability-profile {
max-width: 360px;
}
.capability-diagnostic { .capability-diagnostic {
display: flex; display: flex;
align-items: flex-start; align-items: flex-start;
@@ -7278,64 +7274,8 @@ details.settings-section > :not(summary) + :not(summary) {
} }
} }
.browser-profile-create,
.browser-profile-row {
display: flex;
align-items: flex-end;
gap: var(--space-2);
}
.browser-profile-create .field,
.browser-profile-row .field {
flex: 1;
margin: 0;
}
.browser-profile-list {
display: flex;
flex-direction: column;
gap: var(--space-2);
}
.browser-profile-row {
padding: var(--space-3);
border: 1px solid var(--border-default);
border-radius: var(--radius-card);
background: var(--surface-raised);
}
.browser-profile-default {
display: flex;
align-items: center;
min-height: 32px;
color: var(--text-secondary);
font-size: var(--font-caption);
gap: var(--space-1);
}
.browser-profile-row .danger-ghost {
display: flex;
align-items: center;
min-height: 32px;
padding: 0 var(--space-2);
border: 1px solid var(--danger-border);
border-radius: var(--radius-control);
background: transparent;
color: var(--danger);
cursor: pointer;
font-size: var(--font-caption);
gap: var(--space-1);
}
.browser-profile-row .danger-ghost:disabled {
cursor: not-allowed;
opacity: 0.55;
}
@media (max-width: 720px) { @media (max-width: 720px) {
.capability-diagnostic, .capability-diagnostic,
.browser-profile-create,
.browser-profile-row,
.mcp-server-card__header { .mcp-server-card__header {
align-items: stretch; align-items: stretch;
flex-direction: column; flex-direction: column;