diff --git a/ROADMAP.md b/ROADMAP.md
--- a/ROADMAP.md
+++ b/ROADMAP.md
@@ -137,6 +137,7 @@ app's JSON control channel (`deckcraft --control PORT`).
 
 | Date | What landed |
 |---|---|
+| 2026-10-27 | Report an Issue… (Help menu, File › Account): opens a new GitHub issue with the version, platform and the presentation's file type filled in (never its name or contents) |
 | 2026-10-25 | Windows installer: the Start menu shortcut takes the icon embedded in `deckcraft.exe` instead of an MSI `<Icon>`, so a taskbar pin keeps its icon after an update (#1114); a pin that already lost it has to be unpinned and pinned again once |
 | 2026-10-24 | AutoFormat as you type: a paragraph that starts with `*`, `-`, `--`, `>`, `<>`, `1.`, `1)`, `I.`, `i.`, `A.`, `a.` or `a)` and a space or tab becomes a bulleted or numbered list in that style; Backspace right after puts the typed text back, and Tools ▸ AutoCorrect and `text.autocorrect {lists}` turn it off |
 | 2026-10-24 | Tables: while the caret is in a cell, dragging into another cell selects that range of cells (shaded, merged cells taken in whole), and Shift+arrow grows or shrinks it; formatting applies to the selected cells only, and Delete clears their text and keeps the table |
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
@@ -6,3 +6,5 @@ pub const APPS: &str = "https://getartcraft.com/apps";
 pub const APP_PAGE: &str = "https://getartcraft.com/apps/deckcraft";
 pub const GITHUB: &str = "https://github.com/storytold/deckcraft";
 pub const ISSUES: &str = "https://github.com/storytold/deckcraft/issues";
+/// The new-issue address; Report an Issue… adds the bug form and its prefilled fields.
+pub const NEW_ISSUE: &str = "https://github.com/storytold/deckcraft/issues/new";
diff --git a/crates/ui-egui/src/app/commands.rs b/crates/ui-egui/src/app/commands.rs
--- a/crates/ui-egui/src/app/commands.rs
+++ b/crates/ui-egui/src/app/commands.rs
@@ -215,6 +215,10 @@ impl SlideApp {
                 #[cfg(target_arch = "wasm32")]
                 return Err("updates are handled by the browser: reload the page".into());
             }
+            "app.reportIssue" => {
+                let url = crate::report_issue::url(self);
+                self.open_url(&url);
+            }
             "app.palette" => self.palette = Some((String::new(), 0)),
             "app.preferences" => self.dialog = Some(dialogs::Dialog::new("preferences")),
             "app.openDialog" => self.pick_and_open(),
diff --git a/crates/ui-egui/src/backstage/info.rs b/crates/ui-egui/src/backstage/info.rs
--- a/crates/ui-egui/src/backstage/info.rs
+++ b/crates/ui-egui/src/backstage/info.rs
@@ -237,6 +237,7 @@ pub(super) fn account(app: &mut SlideApp, ui: &mut Ui) {
             }
             ui.hyperlink_to("Join our community on Discord", deckcraft_engine::links::DISCORD);
             ui.hyperlink_to("Source on GitHub", deckcraft_engine::links::GITHUB);
+            ui.hyperlink_to("Report an Issue…", crate::report_issue::url(app));
         });
     });
 }
diff --git a/crates/ui-egui/src/lib.rs b/crates/ui-egui/src/lib.rs
--- a/crates/ui-egui/src/lib.rs
+++ b/crates/ui-egui/src/lib.rs
@@ -42,6 +42,7 @@ mod app;
 mod clock;
 mod ime_hooks;
 pub mod panels;
+mod report_issue;
 mod services;
 mod state;
 
@@ -123,6 +124,7 @@ pub const UI_COMMANDS: &[(&str, &str, Option<&str>, &str)] = &[
     ),
     ("app.about", "About DeckCraft", None, "{}"),
     ("app.checkUpdates", "Check for Updates…", None, "{}"),
+    ("app.reportIssue", "Report an Issue…", None, "{}"),
     ("app.palette", "Command Palette", Some("Cmd+Shift+P"), "{}"),
     ("app.preferences", "Preferences…", Some("Cmd+,"), "{}"),
     ("app.openDialog", "Open…", None, "{purpose?}"),
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
@@ -289,7 +289,15 @@ pub fn menu_tree() -> Vec<(&'static str, Vec<(&'static str, &'static str)>)> {
                 ("Set Up Show…", "show.setup"),
             ],
         ),
-        ("Help", vec![("About DeckCraft", "app.about"), ("Check for Updates…", "app.checkUpdates"), ("Command Palette", "app.palette")]),
+        (
+            "Help",
+            vec![
+                ("About DeckCraft", "app.about"),
+                ("Check for Updates…", "app.checkUpdates"),
+                ("Report an Issue…", "app.reportIssue"),
+                ("Command Palette", "app.palette"),
+            ],
+        ),
     ]
 }
 
diff --git a/crates/ui-egui/src/report_issue.rs b/crates/ui-egui/src/report_issue.rs
new file mode 100644
--- /dev/null
+++ b/crates/ui-egui/src/report_issue.rs
@@ -0,0 +1,46 @@
+//! Help › Report an Issue…: opens a new GitHub issue with the version and system filled in. Nothing
+//! is created until the user submits it.
+
+use crate::SlideApp;
+
+/// File types DeckCraft opens; anything else could be private text, so it is not named.
+const KNOWN_TYPES: &[&str] = &["deckcraft", "pptx", "potx", "ppsx"];
+
+/// The new-issue address. `version` and `environment` are the bug form's field ids, and `body`
+/// fills a blank issue where no form exists. The document is described by its file extension
+/// alone, never a name, path or contents.
+pub(crate) fn issue_url(version: &str, os: &str, document_ext: Option<&str>) -> String {
+    let short = |s: &str| s.chars().filter(|c| !c.is_control()).take(40).collect::<String>();
+    let ext = document_ext.filter(|e| KNOWN_TYPES.contains(e)).unwrap_or("none");
+    let (version, environment) = (short(version), format!("{}; document: {ext}", short(os)));
+    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!("DeckCraft {version}\n{environment}");
+    let (v, e, b) = (encode(&version), encode(&environment), encode(&body));
+    format!("{}?template=bug_report.yml&version={v}&environment={e}&os={e}&body={b}", deckcraft_engine::links::NEW_ISSUE)
+}
+
+/// The address for the open presentation, or for none.
+pub(crate) fn url(app: &SlideApp) -> String {
+    let ext =
+        app.session.active().and_then(|d| d.path.as_deref()).and_then(|p| std::path::Path::new(p).extension()?.to_str()).map(str::to_ascii_lowercase);
+    let os = if cfg!(target_arch = "wasm32") { "Web".into() } else { format!("{} {}", std::env::consts::OS, std::env::consts::ARCH) };
+    issue_url(crate::dialogs::version_line().trim_start_matches("Version "), &os, ext.as_deref())
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]
+    fn the_address_carries_version_and_system_only_and_stays_short() {
+        let url = issue_url("0.1.0", "linux x86_64", Some("pptx"));
+        assert!(url.starts_with("https://github.com/storytold/deckcraft/issues/new?template=bug_report.yml&version=0.1.0&environment=linux%20x86_64%3B%20document%3A%20pptx&os="), "{url}");
+        // Hostile text and an unusual extension can't add a parameter, leak, or grow the address.
+        let url = issue_url(&"\u{1F600}".repeat(99), &"&#=".repeat(99), Some("x.y/Z"));
+        assert_eq!(url.matches('?').count() + url.matches('#').count(), 1, "{url}");
+        assert!(url.is_ascii() && url.contains("document%3A%20none") && url.len() < 2048, "{} bytes: {url}", url.len());
+    }
+}
