diff --git a/ROADMAP.md b/ROADMAP.md
--- a/ROADMAP.md
+++ b/ROADMAP.md
@@ -380,6 +380,7 @@ list) · [roadmap.md](docs/roadmap.md) (milestones, plan) · [parity-checklist.m
 
 Newest first.
 
+- **2026-10-24** · An object's context menu has Generate QR Code…, which fills the frame right-clicked (an empty frame, or a QR code to edit it); it was only in the Object menu. No readiness percentage change.
 - **2026-10-20** · Edit › Keyboard Shortcuts is craft-keymap's shared dialog: commands grouped by menu with Show and Filter, recording that asks before taking a key from another command, undo and redo, named shortcut sets (edits of Default become Custom; an imported set becomes a set of its own), an on-screen US keyboard, InDesign Show Set files reviewed before they apply, JSON export and an HTML summary. ui.json keeps the `shortcuts` map earlier versions read, for the active set. No readiness percentage change.
 - **2026-10-19** · Import InDesign Set… runs on craft-keymap's importer: a set's Window menu panel items (Layers F7, Pages F12, Swatches F5) and Tools items set the panel and tool shortcuts, all of an item's Default keys apply, and the report adds numeric keypad keys read as main keys and likely commands for unmatched items (`window.importShortcuts`). On InDesign's Windows defaults it matches 366 of 1,629 items (was 233) and changes 23 commands. No readiness percentage change.
 - **2026-10-19** · Keyboard shortcuts run on the shared craft-keymap crate. Panels take shortcuts, with InDesign's defaults where the key is free (Swatches F5, Color F6, Layers F7, Info F8, Stroke F10, Paragraph Styles F11, Character Styles Shift+F11, Pages F12, Align Shift+F7); tool keys are editable in Edit › Keyboard Shortcuts and `window.setShortcut`, and tools sharing a key take turns (B). Saved shortcuts load unchanged. No readiness percentage change.
diff --git a/crates/ui-egui/src/canvas.rs b/crates/ui-egui/src/canvas.rs
--- a/crates/ui-egui/src/canvas.rs
+++ b/crates/ui-egui/src/canvas.rs
@@ -5666,6 +5666,54 @@ mod tests {
         assert!(menu_row(&h, "Paste without Formatting").is_some());
     }
 
+    /// Generate QR Code… is in an object's context menu, not only in the Object menu, and fills
+    /// the frame right-clicked (not the one selected before); again on that frame, it edits its
+    /// code in place.
+    #[test]
+    fn an_objects_context_menu_generates_a_qr_code() {
+        let mut app = DesignApp::new(designcraft_engine::Session::new(), crate::Services::default());
+        app.run("file.new", json!({})).unwrap();
+        let other = app.run("frame.create", json!({"rect": [300, 36, 400, 136]})).unwrap()["id"].as_u64().unwrap();
+        let clicked = app.run("frame.create", json!({"rect": [36, 36, 200, 200]})).unwrap()["id"].as_u64().unwrap();
+        app.run("selection.set", json!({"ids": [other]})).unwrap();
+        let mut h = harness(app);
+        // The codes a frame holds, by what they encode.
+        let codes = |h: &Harness<'_, DesignApp>, id: u64| -> Vec<String> {
+            let d = &h.state().session.active().unwrap().doc;
+            d.item(designcraft_doc::ItemId(id))
+                .unwrap()
+                .children()
+                .iter()
+                .filter_map(|c| c.alt_text.strip_prefix("QR code: ").map(str::to_string))
+                .collect()
+        };
+        for content in ["https://example.com", "https://example.org"] {
+            let on_frame = screen_of(&h, 60.0, 60.0);
+            right_click(&mut h, on_frame);
+            // The object menu is taller than the room below the click: scroll it, as the user would.
+            let over = menu_row(&h, "Copy").expect("the object menu").rect().center();
+            h.hover_at(over);
+            h.event(egui::Event::MouseWheel {
+                unit: egui::MouseWheelUnit::Point,
+                delta: vec2(0.0, -2000.0),
+                phase: egui::TouchPhase::Move,
+                modifiers: Default::default(),
+                source: egui::MouseWheelSource::Unknown,
+            });
+            h.run_steps(4);
+            menu_row(&h, "Generate QR Code…").expect("in the object's context menu").click();
+            h.run_steps(3);
+            let app = h.state_mut();
+            let dialog = app.ui.dialog.as_mut().expect("the QR Code dialog");
+            assert_eq!(dialog.id, "qrCode");
+            dialog.fields.insert("content".into(), json!(content));
+            crate::dialogs::confirm(app).unwrap();
+            h.run_steps(2);
+            assert_eq!(codes(&h, clicked), [content.to_string()], "one code, in the frame right-clicked");
+            assert!(codes(&h, other).is_empty(), "the frame selected before is left alone");
+        }
+    }
+
     #[test]
     fn rows_hidden_in_the_menu_bar_are_hidden_in_context_menus() {
         let mut app = DesignApp::new(designcraft_engine::Session::new(), crate::Services::default());
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
@@ -2996,7 +2996,8 @@ const CONTEXT_OBJECT: &[&str] = &[
 ];
 /// Added to the object menu when a graphic frame is selected.
 const CONTEXT_GRAPHIC_FRAME: &[&str] = &["menu:Object/Fitting"];
-const CONTEXT_OBJECT_TAIL: &[&str] = &["menu:Object/Content", "menu:Object/Effects"];
+/// Generate QR Code… fills the selected frame (an empty one, or a QR code to edit it).
+const CONTEXT_OBJECT_TAIL: &[&str] = &["menu:Object/Content", "menu:Object/Effects", "ui:app.qrCode"];
 /// Added to the object menu when a text frame is selected.
 const CONTEXT_TEXT_FRAME: &[&str] = &["-", "cmd:type.fillWithPlaceholder", "cmd:object.textFrameOptions"];
 const CONTEXT_TEXT: &[&str] = &[
