diff --git a/Cargo.lock b/Cargo.lock
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -901,6 +901,7 @@ dependencies = [
  "deckcraft-ui-egui",
  "eframe",
  "egui",
+ "fmv-macos-events",
  "image",
  "log",
  "rfd",
@@ -1631,6 +1632,17 @@ dependencies = [
  "num-traits",
 ]
 
+[[package]]
+name = "fmv-macos-events"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "90642cf66dde7f205dc8b42dfe57b47d0f3a29f571b832742883ef512e23fe55"
+dependencies = [
+ "objc2 0.6.5",
+ "objc2-app-kit 0.3.2",
+ "objc2-foundation 0.3.2",
+]
+
 [[package]]
 name = "fnv"
 version = "1.0.7"
diff --git a/apps/deckcraft/Cargo.toml b/apps/deckcraft/Cargo.toml
--- a/apps/deckcraft/Cargo.toml
+++ b/apps/deckcraft/Cargo.toml
@@ -29,5 +29,12 @@ arboard = { workspace = true, features = ["image-data"] }
 image = { workspace = true }
 cpal = "0.16"
 
+[target.'cfg(target_os = "macos")'.dependencies]
+# Finder double-clicks, Open With and Dock drops arrive as an 'odoc' Apple event, not argv. This
+# small audited crate (also used by GridCraft, PdfCraft and PhotoCraft) registers the handler
+# (Objective-C interop needs unsafe, which our workspace forbids) behind a safe API. Pinned
+# exactly: review the diff before bumping.
+fmv-macos-events = "=0.1.0"
+
 [target.'cfg(all(unix, not(target_os = "macos")))'.dependencies]
 winit = { version = "0.30", default-features = false, features = ["x11"] }
diff --git a/apps/deckcraft/src/apple_events.rs b/apps/deckcraft/src/apple_events.rs
new file mode 100644
--- /dev/null
+++ b/apps/deckcraft/src/apple_events.rs
@@ -0,0 +1,52 @@
+//! macOS open-documents and quit Apple events.
+//!
+//! Finder double-clicks, Open With, drops on the Dock icon and `open -a DeckCraft talk.pptx` don't
+//! pass paths on the command line: LaunchServices sends the running (or just-launched) app a
+//! `kAEOpenDocuments` ('odoc') Apple event. winit 0.30 doesn't handle it and owns the
+//! `NSApplicationDelegate`, so AppKit answered "DeckCraft cannot open files in the Office Open XML
+//! presentation format". Handling it ourselves needs Objective-C class declarations, i.e. `unsafe`,
+//! which this workspace forbids. The audited `fmv-macos-events` crate (also used by GridCraft,
+//! PdfCraft and PhotoCraft) wraps exactly that, an `NSAppleEventManager` handler registered before
+//! Finder's launch event that leaves winit's delegate alone, behind a safe main-thread API.
+
+use deckcraft_ui_egui::SlideApp;
+use fmv_macos_events::{Event, Inbox, Registration};
+
+/// Keeps the Apple-event handlers registered; hold it until the event loop returns.
+pub struct AppleEvents {
+    _registration: Registration,
+    inbox: Inbox,
+}
+
+impl AppleEvents {
+    /// Register the handlers. Call on the main thread before the event loop starts, so the event
+    /// that launched the app (a Finder double-click) is caught too.
+    pub fn install() -> Self {
+        let (registration, inbox) = Registration::install();
+        Self { _registration: registration, inbox }
+    }
+
+    /// The queue the app drains every frame ([`poll`]); events arriving later wake `ctx`.
+    pub fn connect(&self, ctx: &egui::Context) -> Inbox {
+        let ctx = ctx.clone();
+        self.inbox.set_wake(move || ctx.request_repaint());
+        self.inbox.clone()
+    }
+}
+
+/// Open the documents and act on the quit requests that arrived since the last frame. Documents go
+/// through [`SlideApp::open_from_system`], so a double-clicked `.ppsx` starts its slide show as it
+/// does from the command line.
+pub fn poll(inbox: &Inbox, app: &mut SlideApp, ctx: &egui::Context) {
+    for e in inbox.drain() {
+        match e {
+            Event::Open(paths) => {
+                let paths: Vec<String> = paths.iter().filter_map(|p| p.to_str()).map(str::to_string).collect();
+                for (f, e) in app.open_from_system(&paths) {
+                    log::error!("{f}: {e}");
+                }
+            }
+            Event::Quit => ctx.send_viewport_cmd(egui::ViewportCommand::Close),
+        }
+    }
+}
diff --git a/apps/deckcraft/src/main.rs b/apps/deckcraft/src/main.rs
--- a/apps/deckcraft/src/main.rs
+++ b/apps/deckcraft/src/main.rs
@@ -14,6 +14,8 @@
 #![deny(clippy::unwrap_used, clippy::expect_used, clippy::panic, clippy::unimplemented, clippy::todo, clippy::unreachable)]
 #![forbid(unsafe_code)]
 
+#[cfg(target_os = "macos")]
+mod apple_events;
 mod audio;
 mod control_server;
 #[cfg(all(unix, not(target_os = "macos")))]
@@ -27,10 +29,12 @@ mod pasteboard;
 use deckcraft_engine::Session;
 use deckcraft_ui_egui::{Services, SlideApp};
 
-struct App(SlideApp);
+struct App(SlideApp, #[cfg(target_os = "macos")] fmv_macos_events::Inbox);
 
 impl eframe::App for App {
     fn logic(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
+        #[cfg(target_os = "macos")]
+        apple_events::poll(&self.1, &mut self.0, ctx);
         self.0.logic(ctx);
     }
     fn raw_input_hook(&mut self, _ctx: &egui::Context, raw: &mut egui::RawInput) {
@@ -234,6 +238,12 @@ fn main() -> eframe::Result {
     // Before eframe creates the wgpu instance: default Windows to DirectX 12 with FXC (see graphics.rs).
     #[cfg(target_os = "windows")]
     graphics::configure(&mut options, eframe::wgpu::Backends::from_env(), eframe::wgpu::Dx12Compiler::from_env());
+    // Registered before the event loop starts, so it catches the Finder event that launched us as
+    // well as later ones. Lives until the event loop returns; the app creator only borrows it.
+    #[cfg(target_os = "macos")]
+    let apple_events = apple_events::AppleEvents::install();
+    #[cfg(target_os = "macos")]
+    let apple_events = &apple_events;
     eframe::run_native(
         "DeckCraft",
         options,
@@ -264,7 +274,11 @@ fn main() -> eframe::Result {
             if show {
                 app.start_show(0, false);
             }
-            Ok(Box::new(App(app)))
+            Ok(Box::new(App(
+                app,
+                #[cfg(target_os = "macos")]
+                apple_events.connect(&cc.egui_ctx),
+            )))
         }),
     )
 }
@@ -278,7 +292,11 @@ mod tests {
     #[test]
     fn on_exit_ends_the_process_on_macos() {
         if std::env::var_os("DECKCRAFT_TEST_ON_EXIT").is_some() {
-            let mut app = App(SlideApp::new(Session::new(), Services::default()));
+            let mut app = App(
+                SlideApp::new(Session::new(), Services::default()),
+                #[cfg(target_os = "macos")]
+                fmv_macos_events::Inbox::default(),
+            );
             println!("calling on_exit");
             eframe::App::on_exit(&mut app);
             println!("on_exit returned");
diff --git a/packaging/macos/Info.plist.in b/packaging/macos/Info.plist.in
--- a/packaging/macos/Info.plist.in
+++ b/packaging/macos/Info.plist.in
@@ -63,7 +63,7 @@
       <key>UTTypeTagSpecification</key>
       <dict>
         <key>public.filename-extension</key>
-        <array><string>deckcraft</string></array>
+        <array><string>deckcraft</string><string>slidecraft</string></array>
         <key>public.mime-type</key>
         <array><string>application/x-deckcraft</string></array>
       </dict>
@@ -91,5 +91,66 @@
     </dict>
   </array>
 
+  <!-- What Finder may open with DeckCraft (double-click, Open With, default app). Without these
+       AppKit refuses the 'odoc' event with "DeckCraft cannot open files in the … format";
+       apple_events.rs hands the paths to the app. -->
+  <key>CFBundleDocumentTypes</key>
+  <array>
+    <dict>
+      <key>CFBundleTypeName</key>
+      <string>DeckCraft Presentation</string>
+      <key>CFBundleTypeRole</key>
+      <string>Editor</string>
+      <key>LSHandlerRank</key>
+      <string>Owner</string>
+      <key>LSItemContentTypes</key>
+      <array><string>ai.storyteller.deckcraft.presentation</string></array>
+      <key>CFBundleTypeExtensions</key>
+      <array><string>deckcraft</string><string>slidecraft</string></array>
+    </dict>
+    <dict>
+      <key>CFBundleTypeName</key>
+      <string>PowerPoint Presentation</string>
+      <key>CFBundleTypeRole</key>
+      <string>Editor</string>
+      <key>LSHandlerRank</key>
+      <string>Alternate</string>
+      <key>LSItemContentTypes</key>
+      <array><string>org.openxmlformats.presentationml.presentation</string></array>
+      <key>CFBundleTypeExtensions</key>
+      <array><string>pptx</string></array>
+      <key>CFBundleTypeMIMETypes</key>
+      <array><string>application/vnd.openxmlformats-officedocument.presentationml.presentation</string></array>
+    </dict>
+    <dict>
+      <key>CFBundleTypeName</key>
+      <string>PowerPoint Template</string>
+      <key>CFBundleTypeRole</key>
+      <string>Editor</string>
+      <key>LSHandlerRank</key>
+      <string>Alternate</string>
+      <key>LSItemContentTypes</key>
+      <array><string>org.openxmlformats.presentationml.template</string></array>
+      <key>CFBundleTypeExtensions</key>
+      <array><string>potx</string></array>
+      <key>CFBundleTypeMIMETypes</key>
+      <array><string>application/vnd.openxmlformats-officedocument.presentationml.template</string></array>
+    </dict>
+    <dict>
+      <key>CFBundleTypeName</key>
+      <string>PowerPoint Show</string>
+      <key>CFBundleTypeRole</key>
+      <string>Viewer</string>
+      <key>LSHandlerRank</key>
+      <string>Alternate</string>
+      <key>LSItemContentTypes</key>
+      <array><string>org.openxmlformats.presentationml.slideshow</string></array>
+      <key>CFBundleTypeExtensions</key>
+      <array><string>ppsx</string></array>
+      <key>CFBundleTypeMIMETypes</key>
+      <array><string>application/vnd.openxmlformats-officedocument.presentationml.slideshow</string></array>
+    </dict>
+  </array>
+
 </dict>
 </plist>
