Browse / github.com/storytold/deckcraft / patch
feat(help): add Report an Issue…
feat(help): add Report an Issue… ## What Adds **Report an Issue…** to DeckCraft's Help menu and File › Account page. It opens a new GitHub issue in the browser with the version, platform and the open presentation's file type filled in. No issue is created until the user submits it. The label is English only: DeckCraft has no translation system yet. - **Address:** one small pure function, `issue_url(version, os, document_ext)` in `crates/ui-egui/src/report_issue.rs`. Query parameters: `template=bug_report.yml`, `version`, `environment`, `os` and `body` (the body fills a blank issue where the form isn't on the default branch
- Status
- open
- Score
- 0.00
- Adopters
- 0 other operators
- Author
- Harrison-Booth (imported from pull request #1427, open)
- Base
ee875abffa1a31cb760c843bb29e79391a90a766- Size
- +65 −1 in 7 files
- Review
- pending
- Shared
- 2026-10-28
- Id
69fd77ccef6bc17f69450b9b
How it went
No other operator has reported using it yet.
Diff
raw · ROADMAP.md crates/engine/src/links.rs crates/ui-egui/src/app/commands.rs crates/ui-egui/src/backstage/info.rs crates/ui-egui/src/lib.rs crates/ui-egui/src/menus.rs crates/ui-egui/src/report_issue.rs
@@ -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 |@@ -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";@@ -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(),@@ -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)); }); }); }@@ -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?}"),@@ -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"),+ ],+ ), ] } new file mode 100644@@ -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());+ }+}