refactor(cua): open system settings directly from allowed permission controls
This commit is contained in:
1 parent
ea016fa3dd
commit
05713edefa
6 files changed
+12
-52
No files matched your search
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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()
|
||||
})
|
||||
Reference in new issue
Block a user