From appkit
Use when a macOS AppKit app blocks system shutdown/restart or won't quit while showing a sheet or modal, or when it should relaunch exactly where the user left off — restoring open windows, the selected item, and frontmost/minimized/full-screen state. Covers graceful termination (preventsApplicationTerminationWhenModal) and state restoration with NSWindowRestoration. Targets modern macOS.
How this skill is triggered — by the user, by Claude, or both
Slash command
/appkit:appkit-launch-continuityThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
**A great Mac app quits without pushback and comes back as if it was never quit.** People quit apps whenever they want — sometimes the system does it for them, e.g. an overnight reboot for a software update. The app should block quit *only* when it genuinely needs to, and on relaunch restore exactly where the user left off.
A great Mac app quits without pushback and comes back as if it was never quit. People quit apps whenever they want — sometimes the system does it for them, e.g. an overnight reboot for a software update. The app should block quit only when it genuinely needs to, and on relaunch restore exactly where the user left off.
Source: WWDC 2026 Session 289, "Modernize Your AppKit App."
When a window is presenting a sheet, it may not be able to close — and if a window can't close, the app can't quit. The relevant property is:
window.preventsApplicationTerminationWhenModal = false
true — for good reason: it protects unsaved data (e.g. a "save this document?" sheet that genuinely needs a response).false for every sheet or modal that does not strictly require user intervention (inspectors, non-blocking dialogs). That lets the app terminate gracefully.You do not need to inspect the quit reason (
kAEQuitReasonApple Events,applicationShouldTerminate:archaeology) to decide whether to block. Set this one property per-modal based on whether the modal is critical.
NSWindowRestoration)Three steps: opt in → encode UI state → decode to restore windows and UI.
@MainActor class MainWindowController: NSWindowController, NSWindowDelegate {
convenience init() {
let window = NSWindow(/* ... */)
window.identifier = NSUserInterfaceItemIdentifier(WindowIdentifiers.mainWindow)
window.setFrameAutosaveName(WindowIdentifiers.mainWindow)
window.isRestorable = true
window.restorationClass = WindowRestorationHandler.self
}
}
identifier — stable identity for the window.setFrameAutosaveName — for common windows (main, preferences); restores to the same space with the same frame. Not needed for document windows.isRestorable = true — lets AppKit call encodeRestorableState/restoreState, and auto-restore which window was minimized, frontmost, and full-screen.restorationClass — invoked on relaunch to recreate the window.override func encodeRestorableState(with coder: NSCoder) {
super.encodeRestorableState(with: coder) // always call super
coder.encode(selectedProduct?.identifier.uuid.uuidString,
forKey: RestorationKeys.productIdentifier)
}
NSResponders have encodeRestorableState — override it on views too, as needed.invalidateRestorableState():splitViewController.onProductSelected = { [weak self] product in
self?.invalidateRestorableState()
}
AppKit then calls encodeRestorableState on everything invalidated, before quit.
Restore windows in the restoration class. This is called for every window being restored:
class WindowRestorationHandler: NSObject, NSWindowRestoration {
static func restoreWindow(
withIdentifier identifier: NSUserInterfaceItemIdentifier,
state: NSCoder,
completionHandler: @escaping (NSWindow?, Error?) -> Void
) {
if identifier == .mainWindow, let window = appDelegate.mainWindowController?.window {
completionHandler(window, nil)
} else if identifier == .imageWindow {
let controller = ImageWindowController()
appDelegate.imageWindowControllers.append(controller)
completionHandler(controller.window, nil)
} else {
completionHandler(nil, error)
}
}
}
Always call the completion handler — AppKit waits on every restorable window. If creation fails, call it with the error. If you can't call it inside the method, save the handler and call it later, but be absolutely sure to call it.
Then restore the UI for each window with the same coder you encoded into:
override func restoreState(with coder: NSCoder) {
super.restoreState(with: coder)
if let productId = coder.decodeObject(
of: [NSString.self],
forKey: RestorationKeys.productIdentifier) as? String {
splitViewController?.selectedProductId = productId
}
}
applicationShouldTerminate: instead of setting preventsApplicationTerminationWhenModal = false on non-critical modals.restoreWindow(withIdentifier:state:) — AppKit hangs waiting for it.super in encodeRestorableState/restoreState.invalidateRestorableState() — so encodeRestorableState never fires and nothing is saved.Make quit non-blocking for non-critical modals, then save and restore UI state so relaunch feels uninterrupted. See Apple's code sample "Restoring your app's state with AppKit."
Provides behavioral guidelines to reduce common LLM coding mistakes, focusing on simplicity, surgical changes, assumption surfacing, and verifiable success criteria.
Searches, retrieves, and installs Agent Skills from prompts.chat registry using MCP tools like search_skills and get_skill. Activates for finding skills, browsing catalogs, or extending Claude.
npx claudepluginhub markmals/mac-dev-skills --plugin appkit