+ return
{ set('enabled', checked) }} />
{ set('notifyOnTurnCompletion', checked) }} />
@@ -355,10 +384,19 @@ function BrowserOptions({
{copy.networkExposure}
{copy.networkExposureBody}
-
- { requestExposure('loopback') }} selected={selection.networkExposure === 'loopback'} title={copy.loopback} />
- { requestExposure('lan') }} selected={selection.networkExposure === 'lan'} title={copy.lan} />
-
+ {
+ if (value === 'loopback' || value === 'lan') requestExposure(value)
+ }}
+ value={selection.networkExposure}
+ >
+
+
+
}
@@ -467,8 +505,8 @@ export function SetupWizardLanConfirmation({ copy, confirm, cancel }: {
-
-
+ }>{copy.cancelLan}
+
diff --git a/dsh-plugin-desktop/src/native-ui/shared/theme.css b/dsh-plugin-desktop/src/native-ui/shared/theme.css
index eafc2a054f..e0cb93219f 100644
--- a/dsh-plugin-desktop/src/native-ui/shared/theme.css
+++ b/dsh-plugin-desktop/src/native-ui/shared/theme.css
@@ -67,6 +67,7 @@
@layer base {
* { @apply border-border outline-ring/50; }
html, body, #root { width: 100%; height: 100%; }
+ #root { isolation: isolate; }
html.dshDesktopDialogDocument,
html.dshDesktopDialogDocument body,
html.dshDesktopDialogDocument #root { height: auto; min-height: 0; }
diff --git a/dsh-plugin-desktop/tests/native-ui-frame.spec.ts b/dsh-plugin-desktop/tests/native-ui-frame.spec.ts
index 7b609d49c1..b9ff42ceea 100644
--- a/dsh-plugin-desktop/tests/native-ui-frame.spec.ts
+++ b/dsh-plugin-desktop/tests/native-ui-frame.spec.ts
@@ -17,4 +17,8 @@ describe('Desktop-owned native UI frame', () => {
expect(theme).toContain('.dshNativeFrame ~ .dshNativeContent')
expect(theme).not.toContain('.dshNativeFrame + .dshNativeContent')
})
+
+ it('isolates the app root so body-level dialogs cover the high native frame layer', () => {
+ expect(theme).toMatch(/#root\s*\{\s*isolation:\s*isolate;/u)
+ })
})
diff --git a/dsh-plugin-desktop/tests/setup-wizard-native-ui.spec.ts b/dsh-plugin-desktop/tests/setup-wizard-native-ui.spec.ts
index 1a06518b0d..be4e015a41 100644
--- a/dsh-plugin-desktop/tests/setup-wizard-native-ui.spec.ts
+++ b/dsh-plugin-desktop/tests/setup-wizard-native-ui.spec.ts
@@ -16,6 +16,8 @@ import {
SetupWizardStepPage,
SetupWizardSuccess,
} from '../src/native-ui/setup-wizard/App.tsx'
+import { Button } from '../src/native-ui/components/ui/button.tsx'
+import { DialogClose } from '../src/native-ui/components/ui/dialog.tsx'
import { desktopSetupWizardCopy } from '../src/setup-wizard-copy.ts'
const input: DesktopSetupWizardInput = {
@@ -70,6 +72,16 @@ function elementText(node: ReactNode): string {
return elementText((node.props as { readonly children?: ReactNode }).children)
}
+function elementTree(node: ReactNode): readonly ReactElement[] {
+ const elements: ReactElement[] = []
+ Children.forEach(node, child => {
+ if (!isValidElement(child)) return
+ elements.push(child)
+ elements.push(...elementTree((child.props as { readonly children?: ReactNode }).children))
+ })
+ return elements
+}
+
afterEach(() => { vi.unstubAllGlobals() })
describe('Setup Wizard step flow', () => {
@@ -122,10 +134,22 @@ describe('Setup Wizard setting pages', () => {
it.each(['mode', 'material', 'market', 'notifications', 'browser'] as const)(
'lays out the %s page options vertically',
(step) => {
- expect(renderStep(step)).toContain('aria-orientation="vertical"')
+ expect(renderStep(step)).toContain('data-orientation="vertical"')
},
)
+ it.each([
+ ['mode', copy.presentationTitle],
+ ['material', copy.windowMaterial],
+ ['market', copy.marketTitle],
+ ['browser', copy.networkExposure],
+ ] as const)('uses a named shadcn RadioGroup for the %s choices', (step, accessibleName) => {
+ const markup = renderStep(step)
+ expect(markup).toContain('data-slot="radio-group"')
+ expect(markup).toContain(`aria-label="${accessibleName}"`)
+ expect(markup).toContain('data-slot="radio-group-item"')
+ })
+
it('does not combine the plugin market and browser settings', () => {
const market = renderStep('market')
const browser = renderStep('browser')
@@ -238,6 +262,15 @@ describe('Setup Wizard native UI boundaries', () => {
expect(text).toContain('这样很危险,所有在你局域网内的人都能直接操作你的电脑,请谨慎开启')
expect(text).toContain('确认开启局域网访问')
expect(text).toContain('保持仅本机访问')
+ const descendants = elementTree(content)
+ const close = descendants.find(element => element.type === DialogClose)
+ const confirm = descendants.find(element => element.type === Button
+ && elementText(element).includes(copy.confirmLan)
+ && (element.props as { readonly variant?: string }).variant === 'destructive')
+ expect(close).toBeDefined()
+ expect((close?.props as { readonly render?: ReactElement }).render?.props).toMatchObject({ autoFocus: true })
+ expect(confirm).toBeDefined()
+ expect((confirm?.props as { readonly autoFocus?: boolean }).autoFocus).not.toBe(true)
})
it('decodes only the exact bounded state tuple emitted by the owner window', () => {