mirror of
https://github.com/NeuralNomadsAI/CodeNomad.git
synced 2026-10-19 15:34:12 +00:00
fix(tauri): prevent macOS startup window event feedback (#821)
Backport the exact read-only macOS maximize getter from Tao #1182 into the existing 0.34.6 vendor override, preserving the Windows input backport. The original borderless getter changed AppKit style masks and generated geometry events whose persistence capture called the getter again, starving startup independently of OpenCode readiness. Window chrome, persistence policy and shared OpenCode lifecycle remain unchanged. Add a bounded native AppKit regression with isolated original-crate negative-control artifacts, repeated warm-cache runs, getter-phase capture evidence, maximize/restore and fixed-size/decorated cases. Reject unrelated watchdog timeouts and validate runner results with platform-independent tests. Run the native regression in macOS ARM64 CI. Validation includes the macOS Intel VM reproduction and rebuilt Tauri startup, second-window quit/relaunch with isolated OpenCode 2.0.21, 181 macOS and 188 Windows Tauri tests, Windows input negative controls, 44 targeted Electron regressions, successful ARM64 CI tests and a published ARM64 test artifact. Both independent gatekeeper findings were fixed and re-reviewed to zero findings. All test jobs passed; remaining platform builds were still running at the explicitly requested admin merge. Fixes #819.
This commit is contained in:
parent
fb2ae42aeb
commit
46a4ea0a85
13 changed files with 448 additions and 20 deletions
7
.github/workflows/pr-build.yml
vendored
7
.github/workflows/pr-build.yml
vendored
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
83
dev-docs/TAURI_MACOS_STARTUP.md
Normal file
83
dev-docs/TAURI_MACOS_STARTUP.md
Normal file
|
|
@ -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.
|
||||
7
packages/tauri-app/Cargo.lock
generated
7
packages/tauri-app/Cargo.lock
generated
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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" }
|
||||
|
|
|
|||
8
packages/tauri-app/tests/macos-window/Cargo.toml
Normal file
8
packages/tauri-app/tests/macos-window/Cargo.toml
Normal file
|
|
@ -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"
|
||||
134
packages/tauri-app/tests/macos-window/src/main.rs
Normal file
134
packages/tauri-app/tests/macos-window/src/main.rs
Normal file
|
|
@ -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);
|
||||
}
|
||||
28
packages/tauri-app/vendor/README.md
vendored
28
packages/tauri-app/vendor/README.md
vendored
|
|
@ -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
|
||||
|
|
|
|||
54
packages/tauri-app/vendor/patches/tao-1182.patch
vendored
Normal file
54
packages/tauri-app/vendor/patches/tao-1182.patch
vendored
Normal file
|
|
@ -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 {
|
||||
|
|
@ -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 {
|
||||
|
|
|
|||
20
scripts/fixtures/tauri-macos-window-result.mjs
Normal file
20
scripts/fixtures/tauri-macos-window-result.mjs
Normal file
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
31
scripts/test-tauri-macos-window-result.test.mjs
Normal file
31
scripts/test-tauri-macos-window-result.test.mjs
Normal file
|
|
@ -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()))
|
||||
})
|
||||
51
scripts/test-tauri-macos-window.mjs
Normal file
51
scripts/test-tauri-macos-window.mjs
Normal file
|
|
@ -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()
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue