fix(windows): 收敛启动和开发产物路径#48
Conversation
Reviewer's GuideAdjusts Windows build artifacts layout, refines Tauri startup behavior to avoid premature/blank main window on Windows, hardens tray icon creation, and adds Windows helper scripts for dev launch and runtime smoke tests. Sequence diagram for updated Windows startup and main window behaviorsequenceDiagram
actor User
participant OS_Windows
participant OpenLess_Exe
participant Tauri_Core
participant Rust_App as Rust_app_run
participant React_App as React_App_component
User->>OS_Windows: launch openless.exe
OS_Windows->>OpenLess_Exe: create process
OpenLess_Exe->>Tauri_Core: initialize Tauri runtime
Tauri_Core->>Rust_App: run()
Rust_App->>Rust_App: setup_tray_icon
alt default_window_icon available
Rust_App->>Rust_App: TrayIconBuilder::with_id("main-tray")
Rust_App->>Rust_App: .on_menu_event(toggle => show_main_window)
Rust_App->>Rust_App: .on_tray_icon_event(click => show_main_window)
else missing_icon
Rust_App->>Rust_App: log::warn([startup] default window icon missing)
end
Rust_App->>Rust_App: coordinator.bind_app
Rust_App->>Rust_App: coordinator.start_hotkey_listener
alt env.OPENLESS_SHOW_MAIN_ON_START == "1"
Rust_App->>Rust_App: show_main_window(app.handle())
else no_env_flag
Rust_App-->>Tauri_Core: do not show main window yet
end
Tauri_Core->>React_App: mount App(isCapsule=false)
React_App->>React_App: detectOS() => win or other
alt Windows (os == "win")
React_App->>React_App: gate = 'ready'
else macOS or others
React_App->>React_App: gate = 'checking'
end
React_App->>React_App: useEffect(show_main_window_on_mount)
React_App->>React_App: requestAnimationFrame
React_App->>React_App: import @tauri-apps/api/window
React_App->>@tauri-apps/api/window: getCurrentWindow().show()
alt non-Windows (os != "win")
React_App->>React_App: useEffect(permissions_check)
React_App->>React_App: checkAccessibilityPermission()
React_App->>React_App: checkMicrophonePermission()
React_App->>React_App: setGate('ready') when done
end
alt gate == 'checking'
React_App-->>User: render StartupShell
else gate == 'ready'
React_App-->>User: render main UI
end
Sequence diagram for windows-open-dev.ps1 dev launch and foregroundingsequenceDiagram
actor Dev as Developer
participant PS as PowerShell
participant Script as windows_open_dev_ps1
participant OS as Windows_OS
participant OpenLess_Exe as openless_executable
Dev->>PS: run windows-open-dev.ps1
PS->>Script: invoke with optional ExePath
alt ExePath not provided
Script->>Script: resolve appRoot from PSScriptRoot
Script->>Script: ExePath = .artifacts\windows-gnu\dev\openless.exe
end
Script->>Script: verify ExePath exists
Script->>Script: verify WebView2Loader.dll beside ExePath
Script->>Script: Get-Process openless
alt existing process with MainWindowHandle
Script->>Script: Show-OpenLessWindow(existing)
Script-->>Dev: OpenLess is already running - brought to foreground
Script-->>PS: exit 0
else no existing window
Script->>Script: set env.PATH for toolchain
Script->>Script: set env.OPENLESS_SHOW_MAIN_ON_START = "1"
Script->>OS: Start-Process ExePath (working directory = exe folder)
Script->>Script: remove env.OPENLESS_SHOW_MAIN_ON_START
end
loop until deadline or window visible
Script->>OS: Get-Process by id
alt process has MainWindowHandle
Script->>Script: Show-OpenLessWindow(process)
Script-->>Dev: OpenLess started and brought to foreground
Script-->>PS: exit 0
else still headless
Script->>Script: sleep 250ms
end
end
alt deadline reached and no window
Script-->>Dev: throw "no main window visible within 10 seconds"
end
Flow diagram for updated Windows GNU build artifacts pipelineflowchart TD
A_start["Start windows-build-gnu.ps1"] --> B_params["Parse params: MirrorRoot, ArtifactsRoot, KeepMirror"]
B_params --> C_setAppRoot["Resolve appRoot from script root"]
C_setAppRoot --> D_checkArtifactsRoot{"ArtifactsRoot empty?"}
D_checkArtifactsRoot -->|Yes| E_defaultArtifacts["ArtifactsRoot = appRoot/.artifacts/windows-gnu"]
D_checkArtifactsRoot -->|No| F_useProvidedArtifacts["Use provided ArtifactsRoot"]
E_defaultArtifacts --> G_checkSpaces
F_useProvidedArtifacts --> G_checkSpaces["Does appRoot contain spaces?"]
G_checkSpaces -->|Yes| H_mirror["Create MirrorRoot, robocopy appRoot -> MirrorRoot (exclude .artifacts, node_modules, dist, target)"]
H_mirror --> I_setBuildRootMirror["buildRoot = MirrorRoot; usedMirror = true"]
G_checkSpaces -->|No| J_setBuildRoot["buildRoot = appRoot; usedMirror = false"]
I_setBuildRootMirror --> K_env["Set PATH, RUSTUP_TOOLCHAIN, CARGO_BUILD_TARGET"]
J_setBuildRoot --> K_env
K_env --> L_npmDeps{"node_modules exists?"}
L_npmDeps -->|No| M_npmCI["npm ci"]
L_npmDeps -->|Yes| N_skipNpmCI["Skip npm ci"]
M_npmCI --> O_buildDev
N_skipNpmCI --> O_buildDev["npm run tauri build -- --target x86_64-pc-windows-gnu --no-bundle"]
O_buildDev --> P_releaseRoot["releaseRoot = buildRoot/src-tauri/target/x86_64-pc-windows-gnu/release"]
P_releaseRoot --> Q_artifactDevRoot["artifactDevRoot = ArtifactsRoot/dev; mkdir"]
Q_artifactDevRoot --> R_copyExeDev["Copy openless.exe -> artifactDevRoot/openless.exe"]
R_copyExeDev --> S_resolveWebView2["Resolve-WebView2Loader() search under CARGO_HOME/registry/src"]
S_resolveWebView2 --> T_copyWebView2["Copy WebView2Loader.dll -> artifactDevRoot/WebView2Loader.dll"]
T_copyWebView2 --> U_buildBundles["npm run tauri build -- --target x86_64-pc-windows-gnu --bundles msi nsis"]
U_buildBundles --> V_artifactReleaseRoot["artifactReleaseRoot = ArtifactsRoot/release; mkdir"]
V_artifactReleaseRoot --> W_cleanOldExe["Remove existing artifactReleaseRoot/openless.exe if present"]
W_cleanOldExe --> X_checkBundle{"bundle folder exists under releaseRoot?"}
X_checkBundle -->|Yes| Y_copyBundle["Copy releaseRoot/bundle -> artifactReleaseRoot (recursive)"]
X_checkBundle -->|No| Z_skipBundle["Skip bundle copy"]
Y_copyBundle --> AA_cleanupMirror
Z_skipBundle --> AA_cleanupMirror{"usedMirror and not KeepMirror?"}
AA_cleanupMirror -->|Yes| AB_validateMirrorPath{"MirrorRoot under TEMP and leaf == 'openless-windows-gnu'?"}
AB_validateMirrorPath -->|Yes| AC_removeMirror["Remove scratch build root MirrorRoot"]
AB_validateMirrorPath -->|No| AD_warnMirror["Warn: refusing to remove unexpected mirror path"]
AA_cleanupMirror -->|No| AE_noMirrorCleanup["Skip mirror cleanup"]
AC_removeMirror --> AF_printSummary
AD_warnMirror --> AF_printSummary
AE_noMirrorCleanup --> AF_printSummary["Print artifact paths: ArtifactsRoot/dev/openless.exe and release bundles"]
AF_printSummary --> AG_end["End windows-build-gnu.ps1"]
File-Level Changes
Possibly linked issues
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
There was a problem hiding this comment.
Hey - I've found 2 issues, and left some high level feedback:
- In
windows-open-dev.ps1, clearingOPENLESS_SHOW_MAIN_ON_STARTwithRemove-Item Env:OPENLESS_SHOW_MAIN_ON_STARTwill also wipe any pre-existing value from the caller’s environment; consider capturing and restoring the previous value instead of unconditionally removing it. - The
.artifacts\windows-gnupath anddev/releaselayout are now duplicated betweenwindows-build-gnu.ps1,windows-open-dev.ps1andwindows-runtime-smoke.ps1; it may be worth centralizing this convention (e.g. via a small shared helper or constant) to avoid drift if the artifact layout changes again.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- In `windows-open-dev.ps1`, clearing `OPENLESS_SHOW_MAIN_ON_START` with `Remove-Item Env:OPENLESS_SHOW_MAIN_ON_START` will also wipe any pre-existing value from the caller’s environment; consider capturing and restoring the previous value instead of unconditionally removing it.
- The `.artifacts\windows-gnu` path and `dev`/`release` layout are now duplicated between `windows-build-gnu.ps1`, `windows-open-dev.ps1` and `windows-runtime-smoke.ps1`; it may be worth centralizing this convention (e.g. via a small shared helper or constant) to avoid drift if the artifact layout changes again.
## Individual Comments
### Comment 1
<location path="openless-all/app/scripts/windows-runtime-smoke.ps1" line_range="103-104" />
<code_context>
+ Copy-Item -LiteralPath (Resolve-WebView2Loader) -Destination (Join-Path $artifactDevRoot "WebView2Loader.dll") -Force
+
+ npm run tauri build -- --target x86_64-pc-windows-gnu --bundles msi nsis
} finally {
Pop-Location
}
</code_context>
<issue_to_address>
**issue (bug_risk):** Stopping all `openless` processes on the machine may kill unrelated user sessions
The teardown logic appears to call `Get-Process openless | Stop-Process -Force`, which would kill every `openless.exe` on the machine, including existing user sessions. Consider tracking and stopping only the process you launch (e.g. via `$process`), and treating other running instances more gently (e.g. warn instead of force-killing) to avoid side effects.
</issue_to_address>
### Comment 2
<location path="openless-all/app/scripts/windows-runtime-smoke.ps1" line_range="47-52" />
<code_context>
+ }
+}
+
+function Wait-LogPattern($Path, $Pattern, $TimeoutSeconds) {
+ $deadline = (Get-Date).AddSeconds($TimeoutSeconds)
+ while ((Get-Date) -lt $deadline) {
+ if ((Test-Path $Path) -and ((Get-Content -Raw $Path) -match $Pattern)) {
+ return $true
+ }
</code_context>
<issue_to_address>
**suggestion:** Log polling should tolerate transient read errors on the log file
Here `Get-Content -Raw $Path` can intermittently fail (e.g. sharing violations during log rotation or exclusive locks), which will terminate the whole smoke script instead of allowing another poll.
Consider treating these as transient by adding `-ErrorAction SilentlyContinue` or wrapping the read in try/catch and treating failures like a non-match so the loop continues until timeout.
```suggestion
function Wait-LogPattern($Path, $Pattern, $TimeoutSeconds) {
$deadline = (Get-Date).AddSeconds($TimeoutSeconds)
while ((Get-Date) -lt $deadline) {
if (Test-Path $Path) {
try {
$content = Get-Content -Raw -ErrorAction Stop $Path
if ($content -match $Pattern) {
return $true
}
} catch {
# Treat transient read errors (e.g. sharing violations) as non-matches
# and allow the loop to continue until the timeout is reached.
}
}
```
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
| } finally { | ||
| Get-Process openless -ErrorAction SilentlyContinue | Stop-Process -Force |
There was a problem hiding this comment.
issue (bug_risk): Stopping all openless processes on the machine may kill unrelated user sessions
The teardown logic appears to call Get-Process openless | Stop-Process -Force, which would kill every openless.exe on the machine, including existing user sessions. Consider tracking and stopping only the process you launch (e.g. via $process), and treating other running instances more gently (e.g. warn instead of force-killing) to avoid side effects.
| function Wait-LogPattern($Path, $Pattern, $TimeoutSeconds) { | ||
| $deadline = (Get-Date).AddSeconds($TimeoutSeconds) | ||
| while ((Get-Date) -lt $deadline) { | ||
| if ((Test-Path $Path) -and ((Get-Content -Raw $Path) -match $Pattern)) { | ||
| return $true | ||
| } |
There was a problem hiding this comment.
suggestion: Log polling should tolerate transient read errors on the log file
Here Get-Content -Raw $Path can intermittently fail (e.g. sharing violations during log rotation or exclusive locks), which will terminate the whole smoke script instead of allowing another poll.
Consider treating these as transient by adding -ErrorAction SilentlyContinue or wrapping the read in try/catch and treating failures like a non-match so the loop continues until timeout.
| function Wait-LogPattern($Path, $Pattern, $TimeoutSeconds) { | |
| $deadline = (Get-Date).AddSeconds($TimeoutSeconds) | |
| while ((Get-Date) -lt $deadline) { | |
| if ((Test-Path $Path) -and ((Get-Content -Raw $Path) -match $Pattern)) { | |
| return $true | |
| } | |
| function Wait-LogPattern($Path, $Pattern, $TimeoutSeconds) { | |
| $deadline = (Get-Date).AddSeconds($TimeoutSeconds) | |
| while ((Get-Date) -lt $deadline) { | |
| if (Test-Path $Path) { | |
| try { | |
| $content = Get-Content -Raw -ErrorAction Stop $Path | |
| if ($content -match $Pattern) { | |
| return $true | |
| } | |
| } catch { | |
| # Treat transient read errors (e.g. sharing violations) as non-matches | |
| # and allow the loop to continue until the timeout is reached. | |
| } | |
| } |
包含本轮所有合并: - Codex 终审两条 HIGH (cancel race) 修复 (PR #79) - 6 个 Cooper-X-Oak/Codex bot PRs 自动合并 (#44 #49 #53 #68 #72 #73) - 2 个有冲突 PR 本地 rebase 后合并 (#66 cancel + 空转写并存 / #67 Windows docs) - README 破图修复 (PR #80) - workflow-scope 受限的 #48 + #75 由用户在 GitHub UI 直接合并 3 处版本字段同步:package.json + tauri.conf.json + Cargo.toml
摘要
关联 fork 验证:Cooper-X-Oak#12。
本 PR 是从 fork/dev 已验证批次拆出的第二个最小 upstream 维护项:修复 Windows dev artifact 启动路径和启动首屏空壳/白屏体验。它不包含热键 core、ASR、插入 fallback、权限状态等其他 Windows 真机修复。
fork/dev 先行验证
b9ee8b8。修复 / 新增 / 改进
OPENLESS_SHOW_MAIN_ON_START=1显式唤起主窗口。windows-build-gnu.ps1将 dev exe 和WebView2Loader.dll复制到仓库内.artifacts/windows-gnu/dev,bundle 复制到.artifacts/windows-gnu/release。%TEMP%\openless-windows-gnu长期占用磁盘。windows-open-dev.ps1和windows-runtime-smoke.ps1,用于快速唤起和启动链路回归。兼容
.artifacts;.artifacts/已加入 app 级.gitignore。测试计划
windows-build-gnu.ps1、windows-open-dev.ps1、windows-runtime-smoke.ps1openless-all/app/scripts/windows-build-gnu.ps1.artifacts/windows-gnu。.artifacts/windows-gnu/dev/openless.exe与WebView2Loader.dllopenless-all/app/scripts/windows-runtime-smoke.ps1OpenLess,hotkey listener 日志出现。openless-all/app/scripts/windows-open-dev.ps1Summary by Sourcery
Align Windows startup behavior and build artifacts layout, and add helper scripts for Windows dev and runtime smoke verification.
New Features:
Bug Fixes:
Enhancements:
Build:
Tests: