Refuse synthetic control-channel input as OK in Open Hyperlink (#2181)

A ui.key Enter or ui.click on OK reached egui as ordinary input and
could confirm the openLink prompt. A pass that carries synthetic input
now keeps the prompt open and opens nothing.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Higgs32584
2026-11-03 09:48:20 -05:00
co-authored by Claude Opus 5.5
parent 94dd42ba31
commit fe062f57a6
2 changed files with 44 additions and 3 deletions
+24
View File
@@ -1194,6 +1194,30 @@ mod tests {
assert_eq!(opened.borrow().len(), 1);
}
/// #2181: an OK that arrives with synthetic control-channel input (`ui.key` Enter, `ui.click`
/// on OK) opens nothing and keeps the prompt open; the same OK from the user opens the link.
#[test]
fn synthetic_input_cannot_confirm_open_hyperlink() {
use show::{SYNTHETIC_LINK_CONFIRM, confirm_open_link};
use std::{cell::RefCell, rc::Rc};
let opened = Rc::new(RefCell::new(Vec::<String>::new()));
let sink = opened.clone();
let services = crate::Services { open_url: Some(Box::new(move |u: &str| sink.borrow_mut().push(u.to_string()))), ..Default::default() };
let mut session = gridcraft_engine::Session::default();
session.new_workbook();
let mut app = SheetApp::new(session, services);
let d = Dialog::open(&mut app, "openLink", json!({"url": "https://example.org/a"})).unwrap();
app.synthetic_pending = true;
assert!(!confirm_open_link(&mut app, &d), "the prompt stays open");
assert!(opened.borrow().is_empty(), "synthetic input opens nothing");
assert_eq!(app.message.as_ref().map(|m| m.1.as_str()), Some(SYNTHETIC_LINK_CONFIRM));
app.synthetic_pending = false;
app.message = None;
assert!(confirm_open_link(&mut app, &d), "the user's OK closes the prompt");
assert_eq!(*opened.borrow(), vec!["https://example.org/a".to_string()]);
}
#[test]
fn data_validation_without_a_rule_opens_on_a_blank_list() {
+20 -3
View File
@@ -409,9 +409,8 @@ pub fn show(app: &mut SheetApp, ctx: &egui::Context) {
}
if confirm && d.name == "openLink" {
confirm = false;
open = false;
if let Err(e) = open_confirmed_link(app, &d) {
app.message = Some((d.title.clone(), crate::clean_error(&e)));
if confirm_open_link(app, &d) {
open = false;
}
}
if confirm && d.command.is_some() && home_sheet_deleted(app, &mut d) {
@@ -492,6 +491,24 @@ pub fn show(app: &mut SheetApp, ctx: &egui::Context) {
}
}
/// OK in Open Hyperlink; returns whether the prompt closes. A pass that carries control-channel
/// input (`ui.key`, `ui.click`, `ui.drag`) can't say yes for the user (#2181): the link stays
/// unopened and the prompt open, with a message. Real input in that same pass is refused too, so
/// the user just presses OK again.
pub(super) fn confirm_open_link(app: &mut SheetApp, d: &Dialog) -> bool {
if app.synthetic_pending {
app.message = Some((d.title.clone(), SYNTHETIC_LINK_CONFIRM.into()));
return false;
}
if let Err(e) = open_confirmed_link(app, d) {
app.message = Some((d.title.clone(), crate::clean_error(&e)));
}
true
}
/// Why Open Hyperlink ignored an OK that arrived with synthetic control-channel input.
pub(super) const SYNTHETIC_LINK_CONFIRM: &str = "Synthetic input from the control channel can't open a link: press OK yourself to open it.";
/// The user pressed OK in Open Hyperlink: opens the address the prompt showed (`d.link`), never
/// one a caller edited into its values afterwards.
pub(super) fn open_confirmed_link(app: &mut SheetApp, d: &Dialog) -> Result<(), String> {