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:
Huanqi Cao
2026-08-05 00:31:43 +08:00
parent 5c51589665
commit da1b1ff87d
5 changed files with 9 additions and 6 deletions
@@ -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'))