diff --git a/.agents/notes/implemented/bug-fix/2026-08-01-windows-picker-pwsh-dpi.i18n.yaml b/.agents/notes/implemented/bug-fix/2026-08-01-windows-picker-pwsh-dpi.i18n.yaml index f4619714d1..76100a958d 100644 --- a/.agents/notes/implemented/bug-fix/2026-08-01-windows-picker-pwsh-dpi.i18n.yaml +++ b/.agents/notes/implemented/bug-fix/2026-08-01-windows-picker-pwsh-dpi.i18n.yaml @@ -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 diff --git a/.agents/notes/implemented/bug-fix/2026-08-01-windows-picker-pwsh-dpi.md b/.agents/notes/implemented/bug-fix/2026-08-01-windows-picker-pwsh-dpi.md index 2c90821be3..0ca413f575 100644 --- a/.agents/notes/implemented/bug-fix/2026-08-01-windows-picker-pwsh-dpi.md +++ b/.agents/notes/implemented/bug-fix/2026-08-01-windows-picker-pwsh-dpi.md @@ -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 diff --git a/.agents/notes/implemented/bug-fix/2026-08-01-windows-picker-pwsh-dpi.zh.md b/.agents/notes/implemented/bug-fix/2026-08-01-windows-picker-pwsh-dpi.zh.md index ff8a535af3..6842acfc1e 100644 --- a/.agents/notes/implemented/bug-fix/2026-08-01-windows-picker-pwsh-dpi.zh.md +++ b/.agents/notes/implemented/bug-fix/2026-08-01-windows-picker-pwsh-dpi.zh.md @@ -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)所有。 ## 考虑过的替代方案 diff --git a/packages/host/directory-picker-native/src/native-picker.ts b/packages/host/directory-picker-native/src/native-picker.ts index 079211f5a3..0cc12fb38e 100644 --- a/packages/host/directory-picker-native/src/native-picker.ts +++ b/packages/host/directory-picker-native/src/native-picker.ts @@ -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) {', diff --git a/packages/host/directory-picker-native/tests/native-picker.spec.ts b/packages/host/directory-picker-native/tests/native-picker.spec.ts index 8d26c6ab36..707a5cafe4 100644 --- a/packages/host/directory-picker-native/tests/native-picker.spec.ts +++ b/packages/host/directory-picker-native/tests/native-picker.spec.ts @@ -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'))