From 2aa4ff86a43dc307c465626e4e6d818498010ad8 Mon Sep 17 00:00:00 2001 From: Matt Toohey Date: Tue, 13 Oct 2026 15:38:21 +1100 Subject: [PATCH 1/2] =?UTF-8?q?fix(staged):=20list=20overflowed=20footer?= =?UTF-8?q?=20buttons=20first=20in=20the=20timeline=20"=E2=80=A6"=20menu?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Overflowed New note / New commit / New code review items now lead the menu in their footer order, so it reads as a continuation of the button row, with the manual "Write note" action below a separator. This also puts "New code review" above "Write note" as requested. Co-Authored-By: Claude Fable 5.1 Signed-off-by: Matt Toohey --- .../features/timeline/BranchTimeline.svelte | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/apps/staged/src/lib/features/timeline/BranchTimeline.svelte b/apps/staged/src/lib/features/timeline/BranchTimeline.svelte index c3e0d1ba1..c5baab409 100644 --- a/apps/staged/src/lib/features/timeline/BranchTimeline.svelte +++ b/apps/staged/src/lib/features/timeline/BranchTimeline.svelte @@ -1403,14 +1403,9 @@ - {#if onWriteNote} - onWriteNote?.()}> - Write note - - {#if overflowMenuActions.length > 0} - - {/if} - {/if} + {#each overflowMenuActions as action (action)} {#if action === 'note'} {/if} {/each} + {#if onWriteNote} + {#if overflowMenuActions.length > 0} + + {/if} + onWriteNote?.()}> + Write note + + {/if} {/if} From 79ec6299e766c6aacfbfbaf4baea6b76381d6245 Mon Sep 17 00:00:00 2001 From: Matt Toohey Date: Tue, 13 Oct 2026 16:08:09 +1100 Subject: [PATCH 2/2] test(staged): wait out ETXTBSY before exec'ing fake npm in version-probe tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `npm_view_version_reads_the_registry_answer` flaked on ubuntu CI with `None` instead of `Some("4.5.6")`, failing instantly rather than at the 10s bound. On Linux, `fs::write` of the fake `npm` script leaves a write fd open briefly; a test on another thread that forks in that window inherits the fd until its own exec, and `execve` of a file anyone holds open for writing fails with ETXTBSY — which `npm_view_version` rightly reports as unknown. macOS doesn't enforce this, so it never reproduced locally. Route every fake-npm write through a `write_executable` helper that probe-spawns (and immediately kills) the script until exec succeeds, including the hung-lookup script in the failure-modes test. Co-Authored-By: Claude Fable 5.1 Signed-off-by: Matt Toohey --- .../staged/src-tauri/src/managed_acp_tools.rs | 44 +++++++++++++++---- 1 file changed, 36 insertions(+), 8 deletions(-) diff --git a/apps/staged/src-tauri/src/managed_acp_tools.rs b/apps/staged/src-tauri/src/managed_acp_tools.rs index 688723524..3aaeb9d37 100644 --- a/apps/staged/src-tauri/src/managed_acp_tools.rs +++ b/apps/staged/src-tauri/src/managed_acp_tools.rs @@ -1514,16 +1514,44 @@ mod tests { } else { "case \" $* \" in *\" --registry \"*) echo 'unexpected --registry' >&2; exit 43;; esac\n" }; - std::fs::write( - bin_dir.join("npm"), - format!( + write_executable( + &bin_dir.join("npm"), + &format!( "#!/bin/sh\ntest \"$1\" = view || exit 44\ntest \"$3\" = version || exit 45\n{registry_check}echo '{version}'\nexit {exit_code}\n" ), - ) - .unwrap(); + ); + } + + /// Writes `contents` to `path` as a 0755 script and returns only once it + /// can actually be exec'd. On Linux, a test on another thread that forks + /// while our write handle is open inherits it until its own exec, and + /// exec of a file anyone still holds open for writing fails with + /// `ETXTBSY` — which `npm_view_version` would faithfully report as + /// "unknown" and fail the test. Probe-spawn (and immediately kill) the + /// script until that window has passed. + fn write_executable(path: &Path, contents: &str) { + std::fs::write(path, contents).unwrap(); use std::os::unix::fs::PermissionsExt; - std::fs::set_permissions(bin_dir.join("npm"), std::fs::Permissions::from_mode(0o755)) - .unwrap(); + std::fs::set_permissions(path, std::fs::Permissions::from_mode(0o755)).unwrap(); + for _ in 0..1000 { + match std::process::Command::new(path) + .stdin(std::process::Stdio::null()) + .stdout(std::process::Stdio::null()) + .stderr(std::process::Stdio::null()) + .spawn() + { + Ok(mut child) => { + let _ = child.kill(); + let _ = child.wait(); + return; + } + Err(error) if error.kind() == std::io::ErrorKind::ExecutableFileBusy => { + std::thread::sleep(Duration::from_millis(1)); + } + Err(error) => panic!("{} is not executable: {error}", path.display()), + } + } + panic!("{} stayed text-busy", path.display()); } fn path_env(bin_dir: &Path) -> Vec<(String, String)> { @@ -1608,7 +1636,7 @@ mod tests { ); // A hung lookup is cut off at the bound. - std::fs::write(bin.join("npm"), "#!/bin/sh\nsleep 30\necho 9.9.9\n").unwrap(); + write_executable(&bin.join("npm"), "#!/bin/sh\nsleep 30\necho 9.9.9\n"); let started = Instant::now(); assert_eq!( npm_view_version(