diff --git a/.github/workflows/pr-build.yml b/.github/workflows/pr-build.yml index be745ed6c..5e4cb3b74 100644 --- a/.github/workflows/pr-build.yml +++ b/.github/workflows/pr-build.yml @@ -530,6 +530,13 @@ jobs: working-directory: packages/tauri-app/src-tauri run: CODENOMAD_TEST_ZSH=/bin/zsh CODENOMAD_TEST_NODE="$(command -v node)" cargo test --locked -- --test-threads=4 + - name: Reproduce and prevent macOS window event feedback + timeout-minutes: 10 + run: | + node --test scripts/test-tauri-macos-window-result.test.mjs + node scripts/test-tauri-macos-window.mjs --baseline + node scripts/test-tauri-macos-window.mjs --baseline + - name: Test owned Tauri zoom input script run: node --test packages/tauri-app/tests/window-zoom-script.test.mjs diff --git a/AGENTS.md b/AGENTS.md index 6a1d2cb5c..239e8c264 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -47,6 +47,8 @@ - Tauri's Tao Windows input backport lives in `packages/tauri-app/vendor/`; preserve upstream provenance and avoid message pumping under input mutexes. Run `node scripts/test-tauri-input-deadlock.mjs --baseline` on Windows when changing it. See `dev-docs/TAURI_WINDOWS_INPUT_DEADLOCK.md` for the captured failure and override removal criteria. +- Tao's macOS zoom getter must remain read-only: synchronous temporary style-mask changes on borderless windows generate geometry-event feedback and can starve startup navigation. Keep the exact upstream tao#1182 backport beside the Windows fix, with its provenance in `packages/tauri-app/vendor/README.md`. Run `node scripts/test-tauri-macos-window.mjs --baseline` in a graphical macOS session and retain the ARM64 CI regression. See `dev-docs/TAURI_MACOS_STARTUP.md`; do not disable window persistence or change the application chrome as a workaround. + - Verified shared npm OpenCode installations at 2.0.15+ delegate version changes to native `upgrade` through `opencode-update/native-upgrade.ts`, with bundled npm scoped to the verified prefix. Native Windows image retention replaces the write preflight only on that path; first install, older migration and same-version repair retain direct npm/preflight. Never retry a failed native mutation via npm or restart the daemon implicitly. Validate with the isolated `scripts/test-opencode-upgrade-native.mjs` fixture. - Git is a full-functionality prerequisite, with directory-only degraded conversations when the backend cannot find Git. Only the explicitly opened physical folder is session authority in that mode; never infer sibling worktrees from native project IDs. Keep ancestor/descendant mutation identities covered by the deletion fence across Git availability changes. Inform agents through the owned native `codenomad.git-availability` instruction before prompts/custom commands, remove stale context after recovery, and keep this advisory separate from fail-closed environment synchronization. No blocking Git setup UI. Validate with `scripts/test-git-degraded-native.mjs` using an isolated CLI/database and provider. diff --git a/dev-docs/TAURI_MACOS_STARTUP.md b/dev-docs/TAURI_MACOS_STARTUP.md new file mode 100644 index 000000000..6491dbf78 --- /dev/null +++ b/dev-docs/TAURI_MACOS_STARTUP.md @@ -0,0 +1,83 @@ +# macOS startup: borderless maximize getter event feedback + +## Captured failure + +The published development release `v0.20.0-dev-20261001-77692a73` reproduces a +startup hang in a graphical macOS 15.8 (24H23) Intel Hyper-V guest. Its main +thread stays busy in this chain: + +`setup_local_window` geometry event → `capture_window_in_memory` → +`read_window_geometry` → `Window::is_maximized` → Tao `is_zoomed` → +`set_style_mask_sync` → AppKit `-[NSWindow setStyleMask:]`. + +The process remains near one full CPU core. The backend starts independently, +but the native event loop cannot complete loading-screen navigation. The hang +also occurs in a fresh scoped CodeNomad profile with an intentionally missing +OpenCode executable, before OpenCode can connect. The previously installed +0.20.1 application shows the same sampled path. + +This is not the writer-lock/thread explosion fixed for #676. That fix remains +necessary: native getters still run outside client-state locks, and geometry +publication still uses the single debounced worker. + +## Narrow fix and provenance + +Backport the exact macOS source hunk from Tao upstream commit +`2fb512315899973e2c64d00c0e34ed382d93c893` (tao#1182) into the existing +`packages/tauri-app/vendor/tao-0.34.6/` override. The old getter temporarily +changed the borderless window's style mask twice, generating new geometry +events whose capture called the same getter again. The new getter reads the +window and screen frames instead. Titled/resizable windows and missing-screen +fallback retain AppKit's native `isZoomed` query. + +No Tauri dependency/API upgrade, application chrome change, persistence +workaround or shared OpenCode lifecycle change is involved. The Windows input +backport remains intact; Electron uses its own native read-only getter and +requires no production change. + +## Validation + +- `node scripts/test-tauri-macos-window.mjs --baseline` requires a graphical + macOS session. It builds the original published Tao as a negative control in + a temporary workspace and the backport in the real workspace. Both use the + same native fixture, with a bounded watchdog and no application/backend data. + The baseline has its own target directory; the patched artifact is warmed + before it and rechecked afterwards. CI runs the command twice consecutively. + Only a normal getter/read-event assertion or a watchdog in that exact phase + with at least 100 capture callbacks counts as feedback reproduction. Setup, + later-phase and low-evidence timeouts fail the negative control. +- The fixture checks read-generated move/resize events, getters inside geometry + capture, normal bounds, maximize/restore, fixed-size and decorated windows. +- The PR macOS ARM64 job runs the regression in addition to the Tauri crate + tests. Packaging success alone does not qualify this event-loop behavior. +- Run the existing Windows input regression with `--baseline` and the Electron + window/persistence tests to protect cross-host behavior. + +Local non-macOS checks passed: 188 Tauri crate tests on Windows, all six +Windows input cases with their negative controls, and 44 Electron +window/restore/navigation/startup tests. The upstream source patch also passes +`git apply --check --reverse` against the vendored file. + +The native macOS fixture passed in the Intel 15.8 VM: the original published +crate reproduced geometry-event feedback, and the backport passed all +read-only getter and maximize/restore checks. The release Tauri binary was +rebuilt from the `fb2ae42a` development source plus the backport, using the +published release's bundled Node/server/UI resources in a separate test tree. + +The rebuilt package-mode application reaches the interface in the VM (confirmed +in the Hyper-V console by the tester), with the native process at 0.0% CPU at +idle. A second native launch opens a separate window and a synthetic project +using OpenCode 2.0.21 in a separate HOME/XDG/database environment. After a native +AppleScript Quit and relaunch, two retained V3 window records are restored and +the isolated service remains on the same endpoint. No shared daemon or existing +application installation is modified. Guest `screencapture` returns white +frames in this Hyper-V display setup, so those captures are not visual evidence. + +The available local VM is Intel, not Apple Silicon. Direct local ARM64 +reproduction is unavailable; keep that distinction when reporting validation. + +## Override removal + +Remove the workspace override only when the supported resolved Tao release +includes both tao#1182 and tao#1215 and both native negative-control regressions +pass. Do not replace registry-cache sources or patch them at build time. diff --git a/packages/tauri-app/Cargo.lock b/packages/tauri-app/Cargo.lock index 15d496483..5385f4d9d 100644 --- a/packages/tauri-app/Cargo.lock +++ b/packages/tauri-app/Cargo.lock @@ -495,6 +495,13 @@ dependencies = [ "cc", ] +[[package]] +name = "codenomad-macos-window-fixture" +version = "0.1.0" +dependencies = [ + "tao", +] + [[package]] name = "codenomad-tauri" version = "0.20.0" diff --git a/packages/tauri-app/Cargo.toml b/packages/tauri-app/Cargo.toml index 87c1a103b..eee31246b 100644 --- a/packages/tauri-app/Cargo.toml +++ b/packages/tauri-app/Cargo.toml @@ -1,9 +1,10 @@ [workspace] -members = ["src-tauri", "tests/windows-input"] +members = ["src-tauri", "tests/windows-input", "tests/macos-window"] default-members = ["src-tauri"] resolver = "2" -# Tao 0.34.x lacks the upstream Windows input reentrancy fix (tao#1215). +# Tao 0.34.x lacks Windows input reentrancy (tao#1215) and macOS +# read-only zoom detection (tao#1182) fixes. # Keep the current Tauri API/dependency line; see vendor/README.md for provenance. [patch.crates-io] tao = { path = "vendor/tao-0.34.6" } diff --git a/packages/tauri-app/tests/macos-window/Cargo.toml b/packages/tauri-app/tests/macos-window/Cargo.toml new file mode 100644 index 000000000..c73804591 --- /dev/null +++ b/packages/tauri-app/tests/macos-window/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "codenomad-macos-window-fixture" +version = "0.1.0" +edition = "2021" +publish = false + +[target.'cfg(target_os = "macos")'.dependencies] +tao = "=0.34.6" diff --git a/packages/tauri-app/tests/macos-window/src/main.rs b/packages/tauri-app/tests/macos-window/src/main.rs new file mode 100644 index 000000000..49f9b54e2 --- /dev/null +++ b/packages/tauri-app/tests/macos-window/src/main.rs @@ -0,0 +1,134 @@ +// Real AppKit/Tao regression, with no CodeNomad profile, backend or OpenCode. +#[cfg(target_os = "macos")] +fn main() { + use std::{ + sync::{ + atomic::{AtomicUsize, Ordering}, + Arc, + }, + time::{Duration, Instant}, + }; + use tao::{ + dpi::LogicalSize, + event::{Event, WindowEvent}, + event_loop::{ControlFlow, EventLoop}, + window::WindowBuilder, + }; + + let progress = Arc::new((AtomicUsize::new(0), AtomicUsize::new(0))); + let watchdog = progress.clone(); + std::thread::spawn(move || { + std::thread::sleep(Duration::from_secs(20)); + let phase = watchdog.0.load(Ordering::SeqCst); + let captures = watchdog.1.load(Ordering::SeqCst); + if phase == 1 && captures >= 100 { + eprintln!("FAIL: getter event feedback watchdog: phase={phase} captures={captures}"); + } else { + eprintln!("FAIL: unrelated native window timeout: phase={phase} captures={captures}"); + } + std::process::exit(2); + }); + let event_loop = EventLoop::new(); + let window = WindowBuilder::new() + .with_title("CodeNomad isolated macOS window regression") + .with_decorations(false) + .with_inner_size(LogicalSize::new(600.0, 500.0)) + .build(&event_loop) + .unwrap(); + let mut phase = 0; + let mut deadline = Instant::now() + Duration::from_secs(1); + let mut query_during_events = false; + let mut events = 0; + let mut before_reads = 0; + let mut normal_size = window.inner_size(); + let mut normal_position = window.outer_position().unwrap(); + event_loop.run(move |event, _, flow| { + *flow = ControlFlow::WaitUntil(deadline); + match event { + Event::WindowEvent { + event: WindowEvent::Resized(_) | WindowEvent::Moved(_), + .. + } => { + events += 1; + if query_during_events { + progress.1.fetch_add(1, Ordering::SeqCst); + // Same feedback boundary as production geometry capture. + let _ = window.is_maximized(); + } + } + Event::MainEventsCleared if Instant::now() >= deadline => { + match phase { + 0 => { + println!("BEGIN: borderless getter feedback probe"); + progress.0.store(1, Ordering::SeqCst); + normal_size = window.inner_size(); + normal_position = window.outer_position().unwrap(); + assert!(!window.is_maximized()); + query_during_events = true; + before_reads = events; + for _ in 0..100 { + assert!(!window.is_maximized()); + } + } + 1 => { + progress.0.store(2, Ordering::SeqCst); + assert_eq!(events, before_reads, "normal reads generated geometry events"); + assert_eq!(window.inner_size(), normal_size); + assert_eq!(window.outer_position().unwrap(), normal_position); + window.set_maximized(true); + } + 2 => { + assert!(window.is_maximized(), "maximize must remain functional"); + before_reads = events; + for _ in 0..100 { + assert!(window.is_maximized()); + } + } + 3 => { + assert_eq!(events, before_reads, "maximized reads generated geometry events"); + window.set_maximized(false); + } + 4 => { + assert!(!window.is_maximized()); + assert_eq!(window.inner_size(), normal_size, "restore lost normal bounds"); + assert_eq!(window.outer_position().unwrap(), normal_position); + window.set_resizable(false); + } + 5 => { + before_reads = events; + for _ in 0..100 { + assert!(!window.is_maximized()); + } + } + 6 => { + assert_eq!(events, before_reads, "fixed-size reads generated geometry events"); + window.set_resizable(true); + window.set_decorations(true); + } + 7 => { + before_reads = events; + for _ in 0..100 { + assert!(!window.is_maximized()); + } + } + 8 => { + assert_eq!(events, before_reads, "decorated reads generated geometry events"); + println!("PASS: read-only zoom checks, native event capture, maximize/restore and fixed-size/decorated windows"); + *flow = ControlFlow::Exit; + return; + } + _ => unreachable!(), + } + phase += 1; + deadline = Instant::now() + Duration::from_secs(1); + } + _ => {} + } + }); +} + +#[cfg(not(target_os = "macos"))] +fn main() { + eprintln!("Run this native regression on macOS with a graphical session."); + std::process::exit(1); +} diff --git a/packages/tauri-app/vendor/README.md b/packages/tauri-app/vendor/README.md index 1dc32e1d9..f18b5832e 100644 --- a/packages/tauri-app/vendor/README.md +++ b/packages/tauri-app/vendor/README.md @@ -1,7 +1,8 @@ -# Tao Windows input deadlock backport +# Tao native window backports `tao-0.34.6/` is the published Apache-2.0 crate (license included), with only -the four Windows source files from upstream [tao#1215] changed. It is selected +the four Windows source files from upstream [tao#1215] and the macOS +`is_zoomed` function from [tao#1182] changed. It is selected by the Tauri workspace's `[patch.crates-io]`; there is no registry-cache mutation or build-time patching. All platforms keep the same dependency/API version. @@ -30,4 +31,27 @@ Remove this override when the supported Tauri dependency range can resolve a published Tao release containing the fix; run the same regression first. Merely bumping within the unfixed 0.34.x/0.35.x release lines is insufficient. +## macOS read-only zoom detection + +The macOS backport is the exact `window.rs` hunk from upstream commit +`2fb512315899973e2c64d00c0e34ed382d93c893` ([tao#1182]). Its patch is +`patches/tao-1182.patch`. Windows and Linux behavior is unchanged. + +On borderless windows, the old maximize getter temporarily changed the native +style mask twice. These writes generate resize/move events. CodeNomad's geometry +capture reads the getter again, sustaining a main-thread event feedback loop +before loading-screen navigation completes. The upstream getter uses read-only +AppKit frame/screen queries instead. It retains native `isZoomed` for ordinary +titled, resizable windows and when no screen is available. + +Run `node scripts/test-tauri-macos-window.mjs --baseline` in a graphical macOS +session. The isolated native fixture checks that getters generate no geometry +events, including reads during geometry capture, and exercises maximize/restore, +fixed-size and decorated windows. Its original-crate negative control reproduces +the feedback loop. See `dev-docs/TAURI_MACOS_STARTUP.md` for real application tests. + +Remove the override only after both native backports are available in the +resolved supported release and both native regressions pass. + [tao#1215]: https://github.com/tauri-apps/tao/pull/1215 +[tao#1182]: https://github.com/tauri-apps/tao/pull/1182 diff --git a/packages/tauri-app/vendor/patches/tao-1182.patch b/packages/tauri-app/vendor/patches/tao-1182.patch new file mode 100644 index 000000000..6d94cb718 --- /dev/null +++ b/packages/tauri-app/vendor/patches/tao-1182.patch @@ -0,0 +1,54 @@ +From: https://github.com/tauri-apps/tao/commit/2fb512315899973e2c64d00c0e34ed382d93c893 +Subject: fix(macos): remove synchronous styleMask mutations in is_zoomed (#1182) + +Exact upstream source hunk; the upstream .changes entry is not vendored. + +diff --git a/src/platform_impl/macos/window.rs b/src/platform_impl/macos/window.rs +--- a/src/platform_impl/macos/window.rs ++++ b/src/platform_impl/macos/window.rs +@@ -974,24 +974,30 @@ impl UnownedWindow { + } + + pub(crate) fn is_zoomed(&self) -> bool { +- // because `isZoomed` doesn't work if the window's borderless, +- // we make it resizable temporalily. +- let curr_mask = unsafe { self.ns_window.styleMask() }; +- +- let required = NSWindowStyleMask::Titled | NSWindowStyleMask::Resizable; +- let needs_temp_mask = !curr_mask.contains(required); +- if needs_temp_mask { +- self.set_style_mask_sync(required); +- } ++ // Previously, is_zoomed temporarily mutated the window's styleMask(or resizable) ++ // to force macOS to return a valid result for borderless windows. ++ // This synchronous mutation could trigger unnecessary layout passes or state inconsistencies. ++ // Related issue: https://github.com/rust-windowing/winit/issues/4071 and https://github.com/tauri-apps/plugins-workspace/issues/3240 + +- let is_zoomed: bool = unsafe { self.ns_window.isZoomed() }; ++ unsafe { ++ let curr_mask = self.ns_window.styleMask(); + +- // Roll back temp styles +- if needs_temp_mask { +- self.set_style_mask_sync(curr_mask); +- } ++ let required = NSWindowStyleMask::Titled | NSWindowStyleMask::Resizable; ++ if curr_mask.contains(required) { ++ return self.ns_window.isZoomed(); ++ } + +- is_zoomed ++ if let Some(screen) = self.ns_window.screen() { ++ let frame = self.ns_window.frame(); ++ let visible_frame = screen.visibleFrame(); ++ ++ return (frame.size.width - visible_frame.size.width).abs() < 1.0 ++ && (frame.size.height - visible_frame.size.height).abs() < 1.0; ++ } ++ ++ // Fallback to original `isZoomed` check ++ self.ns_window.isZoomed() ++ } + } + + fn saved_style(&self, shared_state: &mut SharedState) -> NSWindowStyleMask { diff --git a/packages/tauri-app/vendor/tao-0.34.6/src/platform_impl/macos/window.rs b/packages/tauri-app/vendor/tao-0.34.6/src/platform_impl/macos/window.rs index 54268dcf4..8a19b9d2c 100644 --- a/packages/tauri-app/vendor/tao-0.34.6/src/platform_impl/macos/window.rs +++ b/packages/tauri-app/vendor/tao-0.34.6/src/platform_impl/macos/window.rs @@ -974,24 +974,30 @@ impl UnownedWindow { } pub(crate) fn is_zoomed(&self) -> bool { - // because `isZoomed` doesn't work if the window's borderless, - // we make it resizable temporalily. - let curr_mask = unsafe { self.ns_window.styleMask() }; + // Previously, is_zoomed temporarily mutated the window's styleMask(or resizable) + // to force macOS to return a valid result for borderless windows. + // This synchronous mutation could trigger unnecessary layout passes or state inconsistencies. + // Related issue: https://github.com/rust-windowing/winit/issues/4071 and https://github.com/tauri-apps/plugins-workspace/issues/3240 - let required = NSWindowStyleMask::Titled | NSWindowStyleMask::Resizable; - let needs_temp_mask = !curr_mask.contains(required); - if needs_temp_mask { - self.set_style_mask_sync(required); + unsafe { + let curr_mask = self.ns_window.styleMask(); + + let required = NSWindowStyleMask::Titled | NSWindowStyleMask::Resizable; + if curr_mask.contains(required) { + return self.ns_window.isZoomed(); + } + + if let Some(screen) = self.ns_window.screen() { + let frame = self.ns_window.frame(); + let visible_frame = screen.visibleFrame(); + + return (frame.size.width - visible_frame.size.width).abs() < 1.0 + && (frame.size.height - visible_frame.size.height).abs() < 1.0; + } + + // Fallback to original `isZoomed` check + self.ns_window.isZoomed() } - - let is_zoomed: bool = unsafe { self.ns_window.isZoomed() }; - - // Roll back temp styles - if needs_temp_mask { - self.set_style_mask_sync(curr_mask); - } - - is_zoomed } fn saved_style(&self, shared_state: &mut SharedState) -> NSWindowStyleMask { diff --git a/scripts/fixtures/tauri-macos-window-result.mjs b/scripts/fixtures/tauri-macos-window-result.mjs new file mode 100644 index 000000000..c0f84fffb --- /dev/null +++ b/scripts/fixtures/tauri-macos-window-result.mjs @@ -0,0 +1,20 @@ +import assert from "node:assert/strict" + +export function verifyNativeWindowResult(result, baseline = false) { + const output = result.stdout + result.stderr + assert.equal(result.error, undefined, String(result.error)) + assert.equal(result.signal, null, output) + if (!baseline) { + assert.equal(result.status, 0, output) + assert.match(result.stdout, /PASS: read-only zoom checks/) + return + } + assert.ok(result.status === 2 || result.status === 101, output) + assert.match(result.stdout, /BEGIN: borderless getter feedback probe/) + if (result.status === 101) { + assert.match(result.stderr, /normal reads generated geometry events/) + } else { + const evidence = result.stderr.match(/FAIL: getter event feedback watchdog: phase=1 captures=(\d+)/) + assert.ok(evidence && Number(evidence[1]) >= 100, output) + } +} diff --git a/scripts/test-tauri-macos-window-result.test.mjs b/scripts/test-tauri-macos-window-result.test.mjs new file mode 100644 index 000000000..40fbe0bc2 --- /dev/null +++ b/scripts/test-tauri-macos-window-result.test.mjs @@ -0,0 +1,31 @@ +import assert from "node:assert/strict" +import { test } from "node:test" +import { verifyNativeWindowResult } from "./fixtures/tauri-macos-window-result.mjs" + +const boundary = "BEGIN: borderless getter feedback probe\n" +const result = (overrides = {}) => ({ status: 2, signal: null, stdout: boundary, + stderr: "FAIL: getter event feedback watchdog: phase=1 captures=1000\n", ...overrides }) + +test("accepts only a getter-boundary feedback watchdog or read-generated event assertion", () => { + verifyNativeWindowResult(result(), true) + verifyNativeWindowResult(result({ status: 101, stderr: "normal reads generated geometry events" }), true) +}) + +test("rejects setup stalls, generic watchdogs, later-phase timeouts and insufficient capture evidence", () => { + for (const invalid of [ + result({ stdout: "" }), + result({ stderr: "FAIL: native window event feedback watchdog" }), + result({ stderr: "FAIL: getter event feedback watchdog: phase=2 captures=1000" }), + result({ stderr: "FAIL: getter event feedback watchdog: phase=1 captures=2" }), + result({ status: 101, stderr: "maximize must remain functional" }), + result({ status: 0 }), + result({ status: null, signal: "SIGTERM" }), + result({ error: new Error("spawn failed") }), + ]) assert.throws(() => verifyNativeWindowResult(invalid, true)) +}) + +test("patched result requires successful exit and native completion evidence", () => { + verifyNativeWindowResult(result({ status: 0, stdout: "PASS: read-only zoom checks", stderr: "" })) + assert.throws(() => verifyNativeWindowResult(result({ status: 0, stdout: "" }))) + assert.throws(() => verifyNativeWindowResult(result())) +}) diff --git a/scripts/test-tauri-macos-window.mjs b/scripts/test-tauri-macos-window.mjs new file mode 100644 index 000000000..ed88d8adc --- /dev/null +++ b/scripts/test-tauri-macos-window.mjs @@ -0,0 +1,51 @@ +// Real AppKit/Tao event feedback. Never opens user profiles or OpenCode. +import assert from "node:assert/strict" +import { spawnSync } from "node:child_process" +import { cp, mkdtemp, readFile, writeFile } from "node:fs/promises" +import os from "node:os" +import path from "node:path" +import { fileURLToPath } from "node:url" +import { verifyNativeWindowResult } from "./fixtures/tauri-macos-window-result.mjs" + +assert.equal(process.platform, "darwin", "Run this regression in a graphical macOS session") +const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..") +const workspace = path.join(root, "packages/tauri-app") +const fixture = path.join(workspace, "tests/macos-window") +const target = path.resolve(process.env.CARGO_TARGET_DIR || path.join(workspace, "target")) +const env = { ...process.env, CARGO_TARGET_DIR: target } +delete env.NODE_OPTIONS +delete env.CODENOMAD_NATIVE_PARENT +function cargo(args, cwd = workspace, targetDirectory = target) { + const result = spawnSync("cargo", args, { cwd, env: { ...env, CARGO_TARGET_DIR: targetDirectory }, encoding: "utf8", timeout: 300_000, maxBuffer: 4 * 1024 * 1024 }) + assert.equal(result.error, undefined, String(result.error)) + assert.equal(result.status, 0, result.stdout + result.stderr) + return result.stdout +} +function check(targetDirectory = target, baseline = false) { + const binary = path.join(targetDirectory, "debug/codenomad-macos-window-fixture") + const result = spawnSync(binary, [], { cwd: fixture, env, encoding: "utf8", timeout: 30_000 }) + verifyNativeWindowResult(result, baseline) + if (baseline) { + console.log("PASS: original Tao getter reproduced geometry event feedback") + } else { + console.log(result.stdout.trim()) + } +} +const metadata = JSON.parse(cargo(["metadata", "--locked", "--format-version", "1"])) +const tao = metadata.packages.filter(item => item.name === "tao") +assert.equal(tao.length, 1) +assert.equal(path.resolve(tao[0].manifest_path), path.join(workspace, "vendor/tao-0.34.6/Cargo.toml")) +// Warm the patched artifact before the negative control to expose output collisions. +cargo(["build", "--locked", "-p", "codenomad-macos-window-fixture"]) +check() +if (process.argv.includes("--baseline")) { + const baseline = await mkdtemp(path.join(os.tmpdir(), "codenomad-macos-zoom-baseline-")) + await cp(path.join(fixture, "src"), path.join(baseline, "src"), { recursive: true }) + await writeFile(path.join(baseline, "Cargo.toml"), (await readFile(path.join(fixture, "Cargo.toml"), "utf8")) + "\n[workspace]\n") + const baselineTarget = path.join(baseline, "target") + cargo(["build", "--manifest-path", path.join(baseline, "Cargo.toml")], baseline, baselineTarget) + // The baseline may exit via the watchdog or the bounded event assertion. + check(baselineTarget, true) + cargo(["build", "--locked", "-p", "codenomad-macos-window-fixture"]) + check() +}