Documents and application lifecycle
Quit decisions, replayable open requests, recent documents, and owned controllers.
Application lifecycle
@legendapp/spark/app supplies getAppContext, activate, hide, quit, beforeQuit, and typed app listeners. Context includes stable identity, native runtime information, and initial process arguments. getAppAvailability() is safe without the optional module.
import { beforeQuit } from '@legendapp/spark/app';
import { settings } from '@legendapp/spark/settings';
const guard = await beforeQuit(async () => {
await settings.set('lastSession', { closedCleanly: true });
return true;
}, { onError: console.error });
await guard.remove();Keep the guard registered for the app-owned lifetime; the final line illustrates teardown. Register before enabling work that needs protection. Save/flush inside the guard. An approved quit can stop JavaScript before an awaiting continuation runs.
Every guard must approve. False, rejection, the native 30-second deadline, or guard changes during a decision vetoes that attempt. quit() joins a pending request and reports quitRequested, with reason: 'vetoed' on refusal. Forced process termination cannot be vetoed. willQuit is a live notification, not an asynchronous cleanup boundary.
App listeners report activation/deactivation, reopen, second-instance arguments, and logical window registry changes. They return synchronous subscriptions. Use /windows listeners for one native window lifetime. finishWindowRestoration() completes the macOS startup-shell restoration coordinator after restored roots are opened.
Open requests and recent history
Use @legendapp/spark/app/documents for file/URL launch requests and recent documents:
import {
subscribeToOpenRequests,
noteRecentDocument,
getRecentDocuments,
} from '@legendapp/spark/app/documents';
const subscription = await subscribeToOpenRequests(request => {
if (request.type === 'file') console.log(request.path);
else console.log(request.url);
});
await noteRecentDocument('/absolute/path/to/Notes.txt');
const recent = await getRecentDocuments();
subscription.remove();OpenRequest discriminates { type: 'file', id, path } from { type: 'url', id, url }. File paths are decoded once; avoid manually interpreting them as URLs. Recent history is project-scoped; native OS history integration depends on the host.
Registration installs the live listener before replaying up to 100 retained native requests and deduplicates replay/live overlap. Each new subscription can replay requests again. Exactly-once import policy across controller lifetimes belongs to the app.
/links separately retains Expo-shaped URL methods and live URL events. Those listeners exclude file-open events and launch replay.
Document coordination
createDocumentAppController owns menu/listener setup, initial opening, reopen requests, document delivery, and selected logical-window state. Its handle exists immediately; await ready for setup. Use onOpenDocument(path, controller) for file requests, and reportError for application callbacks. updateMenus, state getters/setters, and subscribe support application services outside React.
Await controller.remove() at its ownership boundary, including after setup failure. Removal waits for setup/native cleanup, but does not cancel application callbacks already running. Failed cleanup remains retryable.
useDocumentAppController returns loading/ready/error state, keeps callbacks current, and owns removal on unmount. onCleanupError(error, controller) exposes failed cleanup for retry.
watchDocumentReload owns a debounced file watcher, serializes reloads, and waits for an active reload during removal. Its React adapter is useWatchedDocumentReload. Supply onError; do not await watcher removal inside the reload it is waiting for.
See app lifecycle and document coordination.