fix(host): drop the folder-dialog Description both picker modes render badly
.NET 10's modern FolderBrowserDialog renders Description as a bottom strip above the folder input, and the 5.1 classic dialog as an unthemed white box; the property is dropped entirely and a regression assertion pins its absence.
This commit is contained in:
@@ -2,5 +2,5 @@
|
||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write .agents/notes/implemented/bug-fix/2026-08-01-windows-picker-pwsh-dpi.md
|
||||
2026-08-01-windows-picker-pwsh-dpi.md: 2c90821be3e4800d624cb2f54dcd6661756784bc
|
||||
2026-08-01-windows-picker-pwsh-dpi.zh.md: ff8a535af396ed5ede51cbd3c69c1944d28ce95e
|
||||
2026-08-01-windows-picker-pwsh-dpi.md: 0ca413f575b5e2805f29b899e638844916e72573
|
||||
2026-08-01-windows-picker-pwsh-dpi.zh.md: 6842acfc1e1bd9f3342a2bedb878fe2df40449df
|
||||
@@ -10,7 +10,7 @@ The Windows branch of the native directory picker spawned Windows PowerShell 5.1
|
||||
|
||||
## Decision
|
||||
|
||||
The win32 branch in `packages/host/directory-picker-native` now spawns `pwsh.exe` (PowerShell 7) first and falls back to `powershell.exe` (Windows PowerShell 5.1) only when pwsh is missing (`ENOENT`), mirroring the Zenity→KDialog fallback. PowerShell 7's WinForms `FolderBrowserDialog` supports `AutoUpgradeEnabled` (added in .NET Core 3.0, absent from .NET Framework) and renders the modern Explorer-style folder picker. Both runtimes execute the identical script, which calls `SetProcessDPIAware()` (user32) before any window exists, so the dialog is system-DPI-aware no matter which host serves it. `-STA` stays explicit for both, and the fallback keeps the seam's cancellation/failure contract (`null` on cancel, a retryable error otherwise). The host-boundary, RPC trust, and cancellation decisions stay with the [picker feature note](../feature/2026-07-27-native-workspace-directory-picker.md).
|
||||
The win32 branch in `packages/host/directory-picker-native` now spawns `pwsh.exe` (PowerShell 7) first and falls back to `powershell.exe` (Windows PowerShell 5.1) only when pwsh is missing (`ENOENT`), mirroring the Zenity→KDialog fallback. PowerShell 7's WinForms `FolderBrowserDialog` supports `AutoUpgradeEnabled` (added in .NET Core 3.0, absent from .NET Framework) and renders the modern Explorer-style folder picker. Both runtimes execute the identical script, which calls `SetProcessDPIAware()` (user32) before any window exists, so the dialog is system-DPI-aware no matter which host serves it. The script sets no `Description`: .NET 10's modern `FolderBrowserDialog` renders it as a bottom strip above the folder input, and the 5.1 classic dialog as an unthemed box, so the property is dropped entirely. `-STA` stays explicit for both, and the fallback keeps the seam's cancellation/failure contract (`null` on cancel, a retryable error otherwise). The host-boundary, RPC trust, and cancellation decisions stay with the [picker feature note](../feature/2026-07-27-native-workspace-directory-picker.md).
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ Status: implemented
|
||||
|
||||
## 决策
|
||||
|
||||
`packages/host/directory-picker-native` 的 win32 分支现在先启动 `pwsh.exe`(PowerShell 7),仅当 pwsh 缺失(`ENOENT`)时才回退到 `powershell.exe`(Windows PowerShell 5.1),与 Zenity→KDialog 的回退方式一致。PowerShell 7 的 WinForms `FolderBrowserDialog` 支持 `AutoUpgradeEnabled`(.NET Core 3.0 加入;.NET Framework 没有),呈现现代资源管理器风格文件夹选择器。两个运行时执行完全相同的脚本,脚本在任何窗口存在前调用 `SetProcessDPIAware()`(user32),因此无论由哪个宿主服务,对话框都系统 DPI aware。两个运行时都显式保留 `-STA`;回退维持 seam 的取消/失败契约(取消返回 `null`,其余为可重试错误)。宿主边界、RPC 信任与取消决策仍归[选择器功能 Note](../feature/2026-07-27-native-workspace-directory-picker.md)所有。
|
||||
`packages/host/directory-picker-native` 的 win32 分支现在先启动 `pwsh.exe`(PowerShell 7),仅当 pwsh 缺失(`ENOENT`)时才回退到 `powershell.exe`(Windows PowerShell 5.1),与 Zenity→KDialog 的回退方式一致。PowerShell 7 的 WinForms `FolderBrowserDialog` 支持 `AutoUpgradeEnabled`(.NET Core 3.0 加入;.NET Framework 没有),呈现现代资源管理器风格文件夹选择器。两个运行时执行完全相同的脚本,脚本在任何窗口存在前调用 `SetProcessDPIAware()`(user32),因此无论由哪个宿主服务,对话框都系统 DPI aware。脚本不设置 `Description`:.NET 10 的现代 `FolderBrowserDialog` 会把它渲染成文件夹输入框上方的一条底带,5.1 经典对话框则渲染成未主题化的色块,因此该属性被整体移除。两个运行时都显式保留 `-STA`;回退维持 seam 的取消/失败契约(取消返回 `null`,其余为可重试错误)。宿主边界、RPC 信任与取消决策仍归[选择器功能 Note](../feature/2026-07-27-native-workspace-directory-picker.md)所有。
|
||||
|
||||
## 考虑过的替代方案
|
||||
|
||||
|
||||
@@ -68,14 +68,15 @@ export async function pickNativeDirectory(
|
||||
// PowerShell 5.1's FolderBrowserDialog is hardwired to the legacy
|
||||
// SHBrowseForFolder tree; prefer pwsh and fall back only when it is absent.
|
||||
// Both hosts spawn DPI-unaware, so the script opts the process into system
|
||||
// DPI awareness before any window is created.
|
||||
// DPI awareness before any window is created. No Description is set: the
|
||||
// modern dialog renders it as a bottom strip and the classic dialog as an
|
||||
// unthemed box.
|
||||
const script = [
|
||||
"$ErrorActionPreference = 'Stop'",
|
||||
"Add-Type -TypeDefinition 'using System; using System.Runtime.InteropServices; public static class DpiAware { [DllImport(\"user32.dll\")] public static extern bool SetProcessDPIAware(); }'",
|
||||
'[DpiAware]::SetProcessDPIAware() | Out-Null',
|
||||
'Add-Type -AssemblyName System.Windows.Forms',
|
||||
'$dialog = New-Object System.Windows.Forms.FolderBrowserDialog',
|
||||
"$dialog.Description = 'Select Workspace Directory'",
|
||||
'$dialog.ShowNewFolderButton = $true',
|
||||
'$result = $dialog.ShowDialog()',
|
||||
'if ($result -eq [System.Windows.Forms.DialogResult]::OK) {',
|
||||
|
||||
@@ -57,6 +57,8 @@ describe('native directory picker', () => {
|
||||
const script = run.mock.calls[0]?.[1].at(-1)
|
||||
expect(script).toContain("$ErrorActionPreference = 'Stop'")
|
||||
expect(script).toContain('SetProcessDPIAware')
|
||||
// Description renders as a bottom strip (modern) / unthemed box (classic); never set it.
|
||||
expect(script).not.toContain('Description')
|
||||
run.mockResolvedValueOnce({ stdout: '', stderr: '' })
|
||||
await expect(pickNativeDirectory(signal(), { platform: 'win32', run })).resolves.toBeNull()
|
||||
run.mockRejectedValueOnce(failure(1, 'Add-Type failed'))
|
||||
|
||||
Reference in New Issue
Block a user