From dd2b153bbe1adc6744bbb7eb37850ca0a7bdc128 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Hru=C5=A1ka?= Date: Tue, 21 Jul 2026 23:58:57 +0200 Subject: [PATCH] Fix race between spinner animation in weather demo and the network permission prompt --- docs/fs.md | 4 ++++ src/stdlib/perm/prompt.rs | 6 +++++- src/stdlib/tui/output.rs | 7 +++++++ 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/docs/fs.md b/docs/fs.md index 82dd9bc..e106c1b 100644 --- a/docs/fs.md +++ b/docs/fs.md @@ -124,6 +124,10 @@ luna: permission request - No terminal (piped stdin, cron): the operation is denied for this run and *nothing* is recorded. +While a prompt is waiting, `tui` output from concurrently running coroutines +(e.g. a spinner animating during the request that triggered the question) is +held back so it cannot overwrite the question, and resumes with the answer. + ### `access.json` Recorded answers live in `$XDG_CONFIG_HOME/luna/access.json` (usually diff --git a/src/stdlib/perm/prompt.rs b/src/stdlib/perm/prompt.rs index 8d84250..fe54c33 100644 --- a/src/stdlib/perm/prompt.rs +++ b/src/stdlib/perm/prompt.rs @@ -35,8 +35,12 @@ pub(super) fn prompt_access(script: &str, wants: &str) -> Choice { if !can_prompt() { return Choice::NoTty; } + // The leading clear-line erases anything a concurrent coroutine left on + // the current line before the prompt took the terminal (e.g. a spinner + // frame) — further output is held back by the prompt lock, which the + // caller already holds. eprint!( - "{APP_NAME}: permission request\n \ + "\x1b[2K\r{APP_NAME}: permission request\n \ script: {script}\n \ wants: {wants}\n\ [y] allow once [n] deny once [A] allow always [N] never allow: " diff --git a/src/stdlib/tui/output.rs b/src/stdlib/tui/output.rs index 2785ace..21a00d7 100644 --- a/src/stdlib/tui/output.rs +++ b/src/stdlib/tui/output.rs @@ -86,7 +86,14 @@ fn style_opt(lua: &Lua, opts: &Opts, key: &str) -> LuaResult` redraw, which would erase the +/// prompt's question line — blocks until the prompt is answered, then +/// resumes. pub(super) fn write_stdout(bytes: &[u8]) { + let _guard = super::prompts::PROMPT_LOCK.lock().unwrap_or_else(|e| e.into_inner()); #[cfg(test)] print!("{}", String::from_utf8_lossy(bytes)); #[cfg(not(test))]