refactor(cua): open system settings directly from allowed permission controls

This commit is contained in:
creatixchu committed 2026-10-19 21:41:33 +08:00
1 parent ea016fa3dd
commit 05713edefa
6 files changed
+12 -52

No files matched your search

-1
View File
@@ -130,7 +130,6 @@ it.skipIf(process.platform !== 'darwin')('keeps system requests pending until fr
const screen = panel.getByRole('group', { name: 'Screen Recording', exact: true })
expect(await accessibility.getByRole('button').count()).toBe(1)
await accessibility.getByRole('button', { name: 'Allowed', exact: true }).click()
await page.getByRole('menuitem', { name: 'Open System Settings', exact: true }).click()
expect(await screen.getByRole('button').count()).toBe(1)
await expect.poll(() => panel.getAttribute('aria-busy')).toBe('false')
await panel.getByRole('button', { name: 'Revoke', exact: true }).click()
+2 -2
View File
@@ -18,8 +18,8 @@
en: ceb54d964dc355b6
zh: 0892066fbceef7b9
/deepseek-ai-dsh-cua/use-this-package/permission-controls:
en: 096915a67d0d5fd9
zh: 999c004cc923577a
en: be53792edc381ea1
zh: 037407055d9f34a0
/deepseek-ai-dsh-cua/understand-the-implementation:
en: 1fde7df44f040131
zh: 59a842fa402ccb35
+1 -1
View File
@@ -43,7 +43,7 @@ Actions return `dispatched:true, verified:false`; read fresh state to verify the
### Permission controls
The [Computer Use bundle](../cua-bundle/README.md) mounts this page with its native provider. **Authorize Computer Use** groups the new-session default, PTC execution mode, Accessibility, and Screen Recording inside one border, with dividers between the rows. Each system-permission row shows the system-reported responsible application’s `.app` bundle filename and icon together in a bordered label. **Manage applications Computer Use can access** places the application lists inside a separate bordered group. Choose **Request permission**, find that application in the macOS permission list, and enable its switch; if macOS names another application, follow that prompt. With no detected source, the page directs the user to the application named by macOS. Each system-permission row has one control: **Request permission** when access is missing, or an **Allowed** dropdown after authorization. The dropdown’s **Open System Settings** item opens the corresponding privacy pane, where the user can revoke access. Unknown status uses an **Unknown** dropdown; pending initial observations retain a single placeholder. The page refreshes on entry, when the window regains focus, and after permission actions. A failed query offers **Retry**. Follow any macOS quit-and-reopen prompt and restart DSH as needed.
The [Computer Use bundle](../cua-bundle/README.md) mounts this page with its native provider. **Authorize Computer Use** groups the new-session default, PTC execution mode, Accessibility, and Screen Recording inside one border, with dividers between the rows. Each system-permission row shows the system-reported responsible application’s `.app` bundle filename and icon together in a bordered label. **Manage applications Computer Use can access** places the application lists inside a separate bordered group. Choose **Request permission**, find that application in the macOS permission list, and enable its switch; if macOS names another application, follow that prompt. With no detected source, the page directs the user to the application named by macOS. Each system-permission row has one control: **Request permission** when access is missing, or an outlined **Allowed** button with a settings icon after authorization. Clicking it opens the corresponding privacy pane, where the user can revoke access; hovering shows **Open System Settings**. Unknown status uses the same button with an **Unknown** label; pending initial observations retain a single placeholder. The page refreshes on entry, when the window regains focus, and after permission actions. A failed query offers **Retry**. Follow any macOS quit-and-reopen prompt and restart DSH as needed.
System-permission operations affect the computer running DSH. When accessing DSH remotely, complete authorization on that computer.
+1 -1
View File
@@ -43,7 +43,7 @@ kind: "package-reference"
### 权限控件
[Computer Use 组合](../cua-bundle/README.zh.md)将本页与原生 provider 一起挂载。“给 Computer Use 授权”把新会话默认模式、PTC 执行方式、辅助功能和屏幕录制放在同一个边框内,四排之间使用分隔线。两条系统权限要求在能识别责任应用时,把该应用的 `.app` 包名称和图标放在同一个标签中。“管理 Computer Use 可访问的应用”使用独立边框包住应用列表。点击“申请权限”,在 macOS 权限列表中找到该应用并开启对应开关;若 macOS 显示其他名称,以系统提示为准。来源无法识别时,页面引导用户查找系统提示的应用。每条系统权限要求只显示一个控件:未授权时为“申请权限”,已授权时为“已允许”下拉按钮。菜单中的“打开系统设置”进入对应隐私设置页,由用户撤销权限。无法确认状态时显示“未确认”下拉按钮,首次查询期间保留一个占位。进入详情页、返回窗口或完成授权操作后会自动刷新;查询失败时可点击“重试”。若系统要求退出并重新打开应用,请按提示操作并重新启动 DSH。
[Computer Use 组合](../cua-bundle/README.zh.md)将本页与原生 provider 一起挂载。“给 Computer Use 授权”把新会话默认模式、PTC 执行方式、辅助功能和屏幕录制放在同一个边框内,四排之间使用分隔线。两条系统权限要求在能识别责任应用时,把该应用的 `.app` 包名称和图标放在同一个标签中。“管理 Computer Use 可访问的应用”使用独立边框包住应用列表。点击“申请权限”,在 macOS 权限列表中找到该应用并开启对应开关;若 macOS 显示其他名称,以系统提示为准。来源无法识别时,页面引导用户查找系统提示的应用。每条系统权限要求只显示一个控件:未授权时为“申请权限”,已授权时为带设置图标的“已允许”描边按钮,点击直接进入对应隐私设置页,由用户撤销权限,悬停提示“打开系统设置”。无法确认状态时使用同样的按钮,文字为“未确认”。首次查询期间保留一个占位。进入详情页、返回窗口或完成授权操作后会自动刷新;查询失败时可点击“重试”。若系统要求退出并重新打开应用,请按提示操作并重新启动 DSH。
系统权限操作作用于运行 DSH 的电脑。远程访问时,请在该电脑上完成授权。
@@ -1,6 +1,6 @@
/** Native permission status and revocable application consent on the existing plugin page. */
import { useEffect, useState } from 'react'
import { Button, IconChevronDownOutlineRegular, Menu, Modal, Toast } from '@deepseek-ai/dsh-client-ui-primitives'
import { Button, IconSettingsOutlineRegular, Modal, Toast } from '@deepseek-ai/dsh-client-ui-primitives'
import type { InjectFace, PropsLocale, PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots'
import type {} from '@deepseek-ai/dsh-client-ui-plugin-manager/client'
import type { ConfigForm } from '@deepseek-ai/dsh-client-ui-settings/client'
@@ -117,18 +117,13 @@ function PermissionAction({ permission, granted, loading, busy, request, openSet
loading: boolean
busy: boolean
}) {
const [open, setOpen] = useState(false)
useEffect(() => { setOpen(false) }, [busy, loading, granted])
if (loading) return <span className={css.buttonPlaceholder} aria-hidden />
if (granted === false) return <Button size="sm" variant="primary" className={css.permissionAction} disabled={busy}
onClick={() => { request(permission) }}>{t('request')}</Button>
return <Menu open={open && !busy} onClose={() => { setOpen(false) }} align="end" portal autoFocus
anchor={<Button size="sm" variant="outline" className={css.permissionAction} disabled={busy}
aria-haspopup="menu" aria-expanded={open && !busy} onClick={() => { setOpen(value => !value) }}>
{t(granted === true ? 'granted' : 'unknown')}<IconChevronDownOutlineRegular size={12} aria-hidden="true" />
</Button>}
items={[{ id: 'settings', label: t('settings') }]}
onSelect={() => { setOpen(false); openSettings(permission) }} />
return <Button size="sm" variant="outline" className={css.permissionAction} disabled={busy}
title={t('settings')} onClick={() => { openSettings(permission) }}>
{t(granted === true ? 'granted' : 'unknown')}<IconSettingsOutlineRegular size={14} aria-hidden="true" />
</Button>
}
/** @param props Host observation and explicit user actions. @returns Permission controls without automatic system prompts. */
@@ -50,9 +50,9 @@ it('requests only a missing privilege and leaves OS attribution to the system pr
fireEvent.click(screen.getByRole('button', { name: en.request }))
expect(f.props.request).toHaveBeenCalledWith('accessibility')
expect(screen.queryByRole('button', { name: en.settings })).toBeNull()
fireEvent.click(screen.getByRole('button', { name: en.granted }))
expect(f.props.openSettings).not.toHaveBeenCalled()
fireEvent.click(screen.getByRole('menuitem', { name: en.settings }))
const allowed = screen.getByRole('button', { name: en.granted })
expect(allowed.getAttribute('title')).toBe(en.settings)
fireEvent.click(allowed)
expect(f.props.openSettings.mock.calls).toEqual([['screenRecording']])
fireEvent.focus(window)
expect(f.props.refresh).toHaveBeenCalledTimes(2)
@@ -365,39 +365,6 @@ it('loads the runtime setting independently and reports unavailable and read-onl
})
it.each([en, zh])('keeps one control per permission row and closes a granted menu on refresh or revocation', (dictionary) => {
const f = fixture()
f.publish(observe(value))
render(<PermissionPanel {...f.props} t={makeTranslate(dictionary)} />)
const missing = screen.getByRole('group', { name: dictionary.accessibility })
const granted = screen.getByRole('group', { name: dictionary.screenRecording })
expect(within(missing).getAllByRole('button')).toHaveLength(1)
expect(within(missing).getByRole('button', { name: dictionary.request })).toBeTruthy()
const trigger = within(granted).getByRole('button', { name: dictionary.granted })
expect(within(granted).getAllByRole('button')).toHaveLength(1)
trigger.focus()
fireEvent.click(trigger)
expect(trigger.getAttribute('aria-expanded')).toBe('true')
expect(screen.getByRole('menuitem', { name: dictionary.settings })).toBeTruthy()
fireEvent.keyDown(screen.getByRole('menu'), { key: 'Escape' })
expect(screen.queryByRole('menu')).toBeNull()
expect(document.activeElement).toBe(trigger)
fireEvent.click(trigger)
f.publish({ system: { ...f.state.getSnapshot().system, busy: true }, busy: true })
expect(screen.queryByRole('menu')).toBeNull()
expect(trigger.hasAttribute('disabled')).toBe(true)
f.publish(observe(value))
expect(screen.queryByRole('menu')).toBeNull()
fireEvent.click(trigger)
f.publish(observe({ ...value, system: { ...value.system, screenRecording: false } }))
expect(screen.queryByRole('menu')).toBeNull()
expect(within(granted).getAllByRole('button')).toHaveLength(1)
expect(within(granted).getByRole('button', { name: dictionary.request })).toBeTruthy()
expect(f.props.openSettings).not.toHaveBeenCalled()
expect(f.props.revoke).not.toHaveBeenCalled()
})
it('retains the previous single control while a stale query error is being refreshed', () => {
const f = fixture()
f.publish(observe(value))
@@ -406,5 +373,4 @@ it('retains the previous single control while a stale query error is being refre
expect(screen.getByRole('button', { name: en.request }).hasAttribute('disabled')).toBe(true)
expect(screen.getByRole('button', { name: en.granted }).hasAttribute('disabled')).toBe(true)
expect(screen.queryByRole('alert')).toBeNull()
expect(screen.queryByRole('menu')).toBeNull()
})