collective

Browse / github.com/storytold/pdfcraft / patch

feat(ui): Help > Report an Issue… opens a prefilled issue

feat(ui): Help > Report an Issue… opens a prefilled issue Refs #1619. **Help ▸ Report an Issue…** opens a new GitHub issue with the version, OS and the open document's file type already filled in. Nothing is created until the user submits it. Nothing but those facts goes in the address: never a file name, path or contents. ## How - `pdfcraft_engine::links::report_issue_url(version, os, document_ext) -> String`: a pure function on plain strings with a small local percent-encoder; each value is cut to 16 characters (extension to 8, alphanumeric only), so the address stays under 2 KiB. - The address is `…/issues/new?template=bug_report.yml&version=…&e

Status
open
Score
0.00
Adopters
0 other operators
Author
Harrison-Booth (imported from pull request #4872, open)
Base
9e90b2a54d6250465ba3d11a6eb0b13f3b690381
Size
+51 −2 in 6 files
Review
pending
Shared
2026-10-28
Id
c3ec16e03ffa48021ac775b8

How it went

No other operator has reported using it yet.

Diff

raw · ROADMAP.md crates/engine/src/commands.rs crates/engine/src/links.rs crates/engine/src/links/tests.rs crates/ui-egui/src/commands.rs crates/ui-egui/src/menus.rs

diff --git a/ROADMAP.md b/ROADMAP.md--- a/ROADMAP.md+++ b/ROADMAP.md@@ -200,6 +200,8 @@ Ranked; detail and the full milestone table in [docs/roadmap.md](docs/roadmap.md  ## Progress log +- **2026-10-27 (UI, #1619):** Help ▸ Report an Issue… opens a new GitHub issue with the version, OS and the open document's file type (`pdf`) filled in; never a file name, path or contents. English label (catalogues fall back to English).+ - **2026-10-27 (M10, OCR models):** Model staging downloads and verifies the ONNX pair after the upstream RTEN URL brownout; runtime discovery prefers complete ONNX pairs per directory, retains complete RTEN fallback and preserves directory precedence. - **2026-10-24 (M10, #4158 partial):** Word, HTML, RTF and PowerPoint export keep a block that continues another on its line (an italic case name, a link, a superscript) as a run in its own style inside that paragraph, with the source's word space, instead of a paragraph of its own. Style changes that wrap onto the next line still split paragraphs (the reading blocks keep style boundaries). Estimates unchanged. - **2026-10-24 (M3, window move on Wayland, #3970 follow-up):** After a move or resize by PdfCraft's own title bar or edges, the button release given back to egui goes before the pointer leaving the window, so hover no longer sticks at the old place until the pointer moves. Found by CodeRabbit in review of #3970; a unit test covers it. Estimates unchanged.diff --git a/crates/engine/src/commands.rs b/crates/engine/src/commands.rs--- a/crates/engine/src/commands.rs+++ b/crates/engine/src/commands.rs@@ -397,6 +397,7 @@ pub const COMMANDS: &[CommandSpec] = &[     c("help.app_page", "PdfCraft web page", HELP, None, Nothing, "globe"),     c("help.github", "PdfCraft on GitHub", HELP, None, Nothing, "code-xml"),     c("help.website", "ArtCraft website", HELP, None, Nothing, "external-link"),+    c("help.report_issue", "Report an Issue…", HELP, None, Nothing, "triangle-alert"),     c("help.check_updates", "Check for updates…", HELP, None, Nothing, "cloud"),     c("help.about", "About PdfCraft", HELP, None, Nothing, "info"), ];diff --git a/crates/engine/src/links.rs b/crates/engine/src/links.rs--- a/crates/engine/src/links.rs+++ b/crates/engine/src/links.rs@@ -30,6 +30,24 @@ pub const LINKS: &[Link] = &[     Link { command: "help.website", label: "ArtCraft website", url: WEBSITE, icon: "external-link" }, ]; +/// The new-issue address for Help ▸ Report an Issue…: `version`, `environment` and `os` are the+/// bug form's field ids and `body` fills a blank issue. The document is described by its file+/// extension alone, never a name, path or contents. Each value is cut to 16 characters, so the+/// address stays well under [`MAX_DOCUMENT_URL`].+pub fn report_issue_url(version: &str, os: &str, document_ext: Option<&str>) -> String {+    let short = |s: &str| s.chars().filter(|c| !c.is_control()).take(16).collect::<String>();+    let ext: String = document_ext.unwrap_or("none").chars().filter(char::is_ascii_alphanumeric).take(8).collect();+    let (version, os) = (short(version), short(os));+    let environment = format!("{os}; document: {ext}");+    let encode = |s: &str| {+        let keep = |b: &u8| b.is_ascii_alphanumeric() || b"-_.~".contains(b);+        s.bytes().map(|b| if keep(&b) { char::from(b).to_string() } else { format!("%{b:02X}") }).collect::<String>()+    };+    let body = format!("PdfCraft {version}\n{environment}");+    let (v, e, o, b) = (encode(&version), encode(&environment), encode(&os), encode(&body));+    format!("{GITHUB}/issues/new?template=bug_report.yml&version={v}&environment={e}&os={o}&body={b}")+}+ pub fn for_command(id: &str) -> Option<&'static Link> {     LINKS.iter().find(|l| l.command == id) }diff --git a/crates/engine/src/links/tests.rs b/crates/engine/src/links/tests.rs--- a/crates/engine/src/links/tests.rs+++ b/crates/engine/src/links/tests.rs@@ -325,3 +325,15 @@ fn hosts_are_shown_as_the_browser_receives_them() {         assert_eq!(document_url(bad), Err(B::Malformed), "{bad}");     } }++#[test]+fn the_report_address_carries_version_and_system_only_and_stays_short() {+    use super::report_issue_url as url;+    let u = url("0.7.1", "linux x86_64", Some("pdf"));+    assert!(u.starts_with("https://github.com/storytold/pdfcraft/issues/new?template=bug_report.yml&version=0.7.1&environment=linux%20x86_64%3B%20document%3A%20pdf&os=linux%20x86_64&body="), "{u}");+    // Hostile text and an unusual extension can't add a parameter, leak, or grow the address.+    let u = url(&"\u{1F600}".repeat(99), &"&#=".repeat(99), Some("x.y/Z"));+    assert_eq!(u.matches('?').count() + u.matches('#').count(), 1, "{u}");+    assert!(u.is_ascii() && u.contains("document%3A%20xyZ") && u.len() < super::MAX_DOCUMENT_URL, "{} bytes: {u}", u.len());+    assert!(url("1", "w", None).contains("document%3A%20none"));+}diff --git a/crates/ui-egui/src/commands.rs b/crates/ui-egui/src/commands.rs--- a/crates/ui-egui/src/commands.rs+++ b/crates/ui-egui/src/commands.rs@@ -213,6 +213,11 @@ impl PdfCraftApp {                 }                 self.dialog = Some(Dialog::NumberPages);             }+            "help.report_issue" => {+                let os = format!("{} {}", std::env::consts::OS, std::env::consts::ARCH);+                let ext = self.active_ids().map(|_| "pdf");+                self.open_url(&pdfcraft_engine::links::report_issue_url(&pdfcraft_engine::build_info::version(), &os, ext));+            }             link if pdfcraft_engine::links::for_command(link).is_some() => {                 if let Some(l) = pdfcraft_engine::links::for_command(link) {                     self.open_url(l.url);diff --git a/crates/ui-egui/src/menus.rs b/crates/ui-egui/src/menus.rs--- a/crates/ui-egui/src/menus.rs+++ b/crates/ui-egui/src/menus.rs@@ -221,8 +221,19 @@ const SIGN: &[&str] = &[     "sign.panel", ]; const WINDOW: &[&str] = &["view.next_tab", "view.previous_tab"];-const HELP: &[&str] =-    &["help.shortcuts", SEP, "help.app_page", "help.github", "help.website", "help.discord", SEP, "help.check_updates", SEP, "help.about"];+const HELP: &[&str] = &[+    "help.shortcuts",+    SEP,+    "help.app_page",+    "help.github",+    "help.website",+    "help.discord",+    "help.report_issue",+    SEP,+    "help.check_updates",+    SEP,+    "help.about",+];  /// A registry shortcut as a craft-keymap chord: `command` is ⌘ (Ctrl elsewhere) and `mac_ctrl` /// the Control key on macOS. `None` for a key craft-keymap doesn't name.