From 9fc4108899123c0fa7f3e3bca4bb7bcd96ccc13d Mon Sep 17 00:00:00 2001 From: skylar taylor-barrick Date: Mon, 12 Oct 2026 16:50:03 +0200 Subject: [PATCH] Printing: expose native commands through control discovery --- crates/ui-egui/src/control.rs | 35 +++++++++++++++++++++++++++++++++++ docs/control-protocol.md | 3 +++ 2 files changed, 38 insertions(+) diff --git a/crates/ui-egui/src/control.rs b/crates/ui-egui/src/control.rs index 6f6280e..b753b00 100644 --- a/crates/ui-egui/src/control.rs +++ b/crates/ui-egui/src/control.rs @@ -184,6 +184,16 @@ pub fn handle(app: &mut SheetApp, ctx: &egui::Context, method: &str, p: &Json) - ("view.interfaceScale", "Interface Size", "{percent: 75–200 | \"auto\"} (100 = auto; clamped)"), ("ui.dialog", "Open Dialog", ""), ("ui.backstage", "File (Backstage view)", "{page?: home|new|open|info|saveAs|print|export|options|close}"), + ( + "ui.printSettings", + "Print Settings", + "{printer?: discovered name, copies?: 1..999, duplex?: printerDefault|off|longEdge|shortEdge|null, grayscale?: bool, refresh?: bool} → {printer,copies,duplex,grayscale,printers,ready,busy,discovering,status,detail}; {} inspects without submitting", + ), + ( + "ui.printSubmit", + "Submit Print Job", + "existing file.print sheet/pagination params → {printing,bytes,pending}; submits without opening a dialog", + ), ("app.language.set", "Interface Language", "{language: \"en\"|\"zh\"|\"ja\"|\"ko\"|\"ru\"|\"pt\"|\"de\"|\"cs\"} (alias: code)"), ("app.language.english", "Interface Language: English", ""), ("app.language.japanese", "Interface Language: Japanese", ""), @@ -494,4 +504,29 @@ mod tests { assert!(filter_commands(sample(), Some("catalog_no_match_7391")).is_empty()); assert_eq!(filter_commands(sample(), None).len(), 3); } + + #[test] + fn print_command_discovery_documents_settings_and_submission() { + let mut session = gridcraft_engine::Session::new(); + session.new_workbook(); + let mut app = SheetApp::new(session, crate::Services::default()); + let Outcome::Done(reply) = handle(&mut app, &egui::Context::default(), "engine.commands", &json!({"search":"print"})) else { + panic!("command discovery must return immediately"); + }; + assert_eq!(reply["ok"], true); + let commands = reply["result"].as_array().unwrap(); + for (id, expected_docs) in [ + ("ui.printSettings", &["copies", "duplex", "refresh", "discovering", "status", "detail"][..]), + ("ui.printSubmit", &["file.print", "pagination", "pending", "without opening a dialog"][..]), + ] { + let command = commands.iter().find(|command| command["id"] == id).unwrap(); + assert_eq!(command["ui"], true); + let docs = command["params"].as_str().unwrap(); + for expected in expected_docs { + assert!(docs.contains(expected), "{id}: missing {expected}"); + } + } + assert!(!app.printing.busy(), "discovery must not submit a job"); + assert!(app.backstage.is_none(), "discovery must not open settings"); + } } diff --git a/docs/control-protocol.md b/docs/control-protocol.md index 347508e..7cc6ec2 100644 --- a/docs/control-protocol.md +++ b/docs/control-protocol.md @@ -68,6 +68,9 @@ see [`mcp.md`](mcp.md) and [`cli.md`](cli.md). ### Native printing +Desktop `engine.commands {"search":"print"}` (and connected MCP `list_commands`) +lists `ui.printSettings` and `ui.printSubmit` with their parameter and result fields. + `engine.execute` can call `ui.printSettings` with `{}` to inspect the printer list, selected printer, copies, duplex, grayscale, discovery/submission status and backend receipt/error. `{"refresh":true}` starts background discovery; poll the same command until `discovering` is