# AI execution Run installed Claude/Codex tools or use the optional macOS Codex supervisor. Source: https://legend.so/spark/ai/ Legend Spark: Experimental — not for production. These guides cover source SDK APIs; the published preview can differ. [Check release status.](https://legend.so/spark/limitations/) ## [Installed command-line tools](https://legend.so/spark/ai/#installed-command-line-tools) `@legendapp/spark/ai` owns execution helpers for installed Claude and Codex CLIs. It uses Spark processes and requires a desktop process host. Installation, login, prompts, provider policy, and data validation remain application/user responsibilities. Importing helpers does not run a prompt. ```ts import { getAICommandAvailability, runAITool, parseAIJson } from '@legendapp/spark/ai'; const availability = await getAICommandAvailability({ preferredTool: 'codex' }); if (availability.preferredTool) { const result = await runAITool({ tool: availability.preferredTool, prompt: 'Return a JSON object containing a greeting.', timeoutMs: 30_000, }); if (result.exit.type === 'exited' && result.exit.code === 0) { const value = parseAIJson(result.output); console.log(value); } } ``` Availability is an advisory installed-command probe, not authenticated access. `runAITool` retains the process result, including raw stdout/stderr bytes, discriminated exit, timeout/abort, and truncation. `output` is trimmed UTF-8 stdout, falling back to stderr if stdout is empty. Nonzero exit and termination are results; operational failures reject. `signal`, `cwd`, and `timeoutMs` follow the process contract. Codex model/reasoning settings go in `invocationOptions.codex`. The Codex exec recipe uses ephemeral execution and ignores user config/exec-policy rule files while retaining the CLI's sandbox/approval mechanism. Protocol/flags depend on the installed tool version. `buildAIInvocation` returns the argument recipe without execution. JSON helpers extract raw/fenced/prose-wrapped content and return unknown/null; validate application schemas before using it. `formatAIErrorOutput(output, { maxLength })` bounds error previews. No React mount is required. ## [Native Codex app-server](https://legend.so/spark/ai/#native-codex-app-server) `@legendapp/spark/ai/codex` supplies an optional **macOS** native supervisor, with exec fallback when its app-server protocol is unavailable. `getCodexAvailability()` safely reports unsupported hosts/missing modules and installed-command information. A consuming app needs the matching native capability and rebuilt host. ```ts import { runCodexPrompt, shutdownCodex } from '@legendapp/spark/ai/codex'; const result = await runCodexPrompt('Return a greeting as JSON.', { outputSchema: { type: 'object', properties: { greeting: { type: 'string' } }, required: ['greeting'], additionalProperties: false, }, reasoningEffort: 'low', timeoutMs: 120_000, }); console.log(result.output); await shutdownCodex(); ``` The final line represents application-owned shutdown. The supervisor is process-wide. `cancelActiveCodexRuns()` cancels all accepted runs, including startup, and returns a count. `shutdownCodex()` cancels active runs and stops the server; later execution restarts it. These are not per-component or per-request cleanup operations. This backend does not promise a per-request AbortSignal. `timeoutMs` is a whole number from 1,000 to 86,400,000 milliseconds. The isolated Codex home reuses existing authentication; the library does not acquire credentials. Pre-ack notification buffering is bounded at 16 MiB and excessive buffering fails explicitly. See [the AI guide](https://github.com/LegendApp/legend-spark/blob/main/docs/ai.md). [View Markdown](https://legend.so/spark/ai.md)[Report a documentation correction](https://github.com/LegendApp/legend-docs/issues/new?title=Docs%3A+AI+execution&body=Page%3A+https%3A%2F%2Flegend.so%2Fspark%2Fai%0A%0AWhat+is+incorrect+or+missing%3F%0A%0AExpected+behavior+or+supporting+source%3A%0A) --- # Application configuration Own desktop configuration, compose Expo projects, and preserve native identity. Source: https://legend.so/spark/configuration/ Legend Spark: Experimental — not for production. These guides cover source SDK APIs; the published preview can differ. [Check release status.](https://legend.so/spark/limitations/) ## [Spark-owned projects](https://legend.so/spark/configuration/#spark-owned-projects) Edit `desktop.config.json`; generated `app.json` is Expo's transport configuration. Universal projects use a generated `app.config.js` resolver. Retain the generated stable `projectId`: it scopes storage and runtime identity across app renames and builds. ```json { "$schema": "./node_modules/@legendapp/spark/schema.json", "projectId": "f478dff4-f9a1-4ff2-8096-64df89e1c470", "name": "MyApp", "version": "0.0.1", "platforms": ["macos"], "window": { "size": { "width": 1000, "height": 700 }, "minSize": { "width": 500, "height": 300 }, "restoreBounds": true }, "macos": { "bundleIdentifier": "com.example.myapp" } } ``` Sizes are outer-frame logical dimensions. AppKit-specific chrome is grouped under `window.macos`, including `titleBar.trafficLights` and `backgroundMaterial`. Portable defaults stay in `window`; native startup behavior stays in `macos.lifecycle`. Spark owns identity, windows, document associations, update signing, helper bundles, and desktop lifecycle policy. Expo settings/plugins go in `expo` or `expoByPlatform.`. Custom native settings may require an app-specific binary. `@legendapp/spark/config` exports typed configuration/readers. `toExpo` validates untrusted Spark JSON. The schema supports editor checks; runtime validation also checks target, size, helper-path, and update relationships. ## [Existing Expo apps](https://legend.so/spark/configuration/#existing-expo-apps) From a prepared local SDK checkout: ```sh bun run spark add desktop --project /absolute/path/to/ExistingExpoApp ``` The integration checks the installed Expo `54.0.37`, React Native `0.81.6`, and React `19.1.4` baseline before editing. Upgrading an incompatible app is separate work. It preserves the entry point, source, existing mobile/web scripts, and mobile native projects. Existing desktop script names are preserved, using `spark:macos`/`spark:windows` when needed. Expo remains the source for app name/version and shared settings. The sidecar declares `extends: "expo"`, stable identity, desktop platforms, and desktop options. `withSparkExpo` from `/expo-config` composes the existing config; ordinary Expo commands retain their mobile/web meaning. Spark commands select the desktop/shared session environment automatically. Review generated integration files and run the app's existing checks. Automatic config composition has restrictions on export shapes, module format, and Metro/native config filenames. Existing desktop projects need explicit host composition. See [the integration guide](https://github.com/LegendApp/legend-spark/blob/main/docs/add-desktop.md); preserving an Expo Router entry does not establish desktop Router support. ## [Metro and native discovery](https://legend.so/spark/configuration/#metro-and-native-discovery) Retain the generated wrappers and apply application customizations through them. For a Spark-owned project: ```js // metro.config.js const { metroConfig } = require('@legendapp/spark/metro'); module.exports = metroConfig(__dirname); ``` ```js // react-native.config.js module.exports = require('@legendapp/spark/native').nativeConfig(__dirname); ``` For an existing Expo project, compose its configuration: ```js const { getDefaultConfig, withSparkMetro } = require('@legendapp/spark/metro'); const config = getDefaultConfig(__dirname); module.exports = withSparkMetro(config); ``` `withSparkNative(existingConfig, __dirname)` preserves app assets and native settings. `withSparkMetro` accepts asynchronous wrappers as well. ## [Native lifecycle](https://legend.so/spark/configuration/#native-lifecycle) The standard Spark AppDelegate owns React bootstrap and app event dispatch. `macos.lifecycle.mainWindow` can configure initial visibility, close behavior, and reopen policy. Native packages can opt into startup plugins and asynchronous quit handlers without replacing the app delegate. Link/include a plugin that must survive production pruning. A hidden main window keeps the runtime alive; it does not make the app menu-bar-only. Runtime close/quit guards and [document coordination](https://legend.so/spark/documents/) own asynchronous application decisions. See [native lifecycle](https://github.com/LegendApp/legend-spark/blob/main/docs/host-migration.md) and [configuration details](https://github.com/LegendApp/legend-spark/blob/main/docs/configuration.md). [View Markdown](https://legend.so/spark/configuration.md)[Report a documentation correction](https://github.com/LegendApp/legend-docs/issues/new?title=Docs%3A+Application+configuration&body=Page%3A+https%3A%2F%2Flegend.so%2Fspark%2Fconfiguration%0A%0AWhat+is+incorrect+or+missing%3F%0A%0AExpected+behavior+or+supporting+source%3A%0A) --- # Development and runtimes Use Expo CLI, select Spark Runner or custom binaries, and rebuild native changes. Source: https://legend.so/spark/development/ Legend Spark: Experimental — not for production. These guides cover source SDK APIs; the published preview can differ. [Check release status.](https://legend.so/spark/limitations/) `npm run dev`, `npm start`, and the generated desktop scripts enter one managed Expo CLI development session. Expo owns Metro, logs, reload, debugging, and mobile/web actions; Spark adds desktop runtime selection and compatibility checks. ## [Terminal actions](https://legend.so/spark/development/#terminal-actions) | Key | Action | | --- | --- | | `d` | Open the host desktop target | | `g` | Switch Spark Runner/custom development build without compiling | | `b` | Build and open a required custom development binary | | `r`, `j`, `m` | Expo reload, debugger, and development menu | | `i`, `a`, `w` | Declared iOS, Android, and web targets | | `s` | Mobile Expo Go/development-client selection | | `?` | Current command table | | Ctrl+C | Exit the session and close its owned native app | Expo Go is a mobile runtime; it is separate from Spark Runner. Switching binaries or restarting Metro can reset React state. Desktop selection is remembered in `.spark/settings.json`. A universal session serves all declared targets. A missing/incompatible desktop binary blocks that desktop bundle while mobile/web remain available. `--platform` chooses an initial launch; it does not add a platform to the project. ```sh npx --no-install spark dev --no-open npx --no-install spark dev --platform ios npx --no-install spark dev --port 8082 --localhost ``` Expo start flags such as `--clear`, `--offline`, `--lan`, `--tunnel`, and `--max-workers` pass through. Use `spark dev --help` for the full list. Desktop launch uses the port Expo reports. ## [Native changes](https://legend.so/spark/development/#native-changes) Extra native modules, URL/document associations, helper binaries, or native settings/plugins can require an app-specific build. Spark checks native requirements before loading JavaScript; it does not automatically compile every edit. ```sh npx --no-install spark build --dev --platform macos # On Windows: npx --no-install spark build --dev --platform windows ``` `build --dev --force` forces regeneration and compilation. Author durable native changes in packages/config plugins; generated native directories are disposable. Run native generation/build operations sequentially in one checkout. To select Intel macOS from a consumer project: ```sh SPARK_MACOS_ARCH=x64 npx --no-install spark build --dev SPARK_MACOS_ARCH=x64 npx --no-install spark dev ``` `SPARK_MACOS_ARCH=arm64|x64` and `SPARK_WINDOWS_ARCH=x64|arm64` select build targets. The matching architecture is part of runtime compatibility. Intel macOS and Windows native builds and runtime behavior still need validation. No hosted Windows Runner exists; build one locally. ## [Production selection](https://legend.so/spark/development/#production-selection) ```sh npx --no-install spark analyze npm run build ``` Analysis records production reachability in `.spark/selection-report.json`. SDK native modules can be pruned; unknown third-party native dependencies are retained. Keep native-only requirements in configuration. `build --preview` uses the production native selection in Debug; it is distinct from the standalone embedded-bundle build. ## [SDK iteration and diagnostics](https://legend.so/spark/development/#sdk-iteration-and-diagnostics) Repack after SDK source changes and rebuild/register a Runner when its native signature changes. From the framework checkout, `node scripts/refresh-consumer.ts /path/to/app` refreshes a local test consumer's SDK archives. Content-hashed archives avoid stale package-manager cache identities. Use real packed consumers for distribution checks. `spark doctor` checks native prerequisites and dependency pins. `spark upgrade` updates the project's dependency pins. Run them from your app: ```sh npx --no-install spark doctor npx --no-install spark upgrade ``` Generated diagnostics live under `.spark/`: command records, full build logs, native selection, compatibility session state, and product/fingerprint records. Keep those outputs out of version control. See [configuration](https://legend.so/spark/configuration/), [examples and validation](https://legend.so/spark/examples/), and the [SDK development guide](https://github.com/LegendApp/legend-spark/blob/main/docs/development.md). [View Markdown](https://legend.so/spark/development.md)[Report a documentation correction](https://github.com/LegendApp/legend-docs/issues/new?title=Docs%3A+Development+and+runtimes&body=Page%3A+https%3A%2F%2Flegend.so%2Fspark%2Fdevelopment%0A%0AWhat+is+incorrect+or+missing%3F%0A%0AExpected+behavior+or+supporting+source%3A%0A) --- # Packaging and updates Standalone macOS binaries, signed archives, native app updates, and SDK transfer. Source: https://legend.so/spark/distribution/ Legend Spark: Experimental — not for production. These guides cover source SDK APIs; the published preview can differ. [Check release status.](https://legend.so/spark/limitations/) ## [Build and package a macOS app](https://legend.so/spark/distribution/#build-and-package-a-macos-app) From the generated app: ```sh npm run build npx --no-install spark open npm run package ``` `build` embeds JavaScript and the production-selected native graph in a local ad-hoc-signed `.app`. `package` signs a staging copy with Developer ID, notarizes it with your Keychain profile, and validates the distribution ZIP. It does not publish the application. Signing requires installed credentials, full Xcode tooling, and a notarization profile. First use can configure credentials. Keep signed archives and submission state until Apple's outcome is known. Resume/recover a pending submission through the documented packaging commands rather than starting a duplicate submission. Intel targeting uses `SPARK_MACOS_ARCH=x64` for build/package, with matching native dependencies and helper binaries. Intel native builds, runtime behavior, and signed distribution still need validation. Windows production/preview builds, signing/MSIX, Linux, and the Mac App Store workflow remain unsupported. See [packaging details](https://github.com/LegendApp/legend-spark/blob/main/docs/packaging.md). ## [Whole-app updates](https://legend.so/spark/distribution/#whole-app-updates) `@legendapp/spark/updates` supplies native macOS whole-app update integration. It is not Expo-style JavaScript OTA updates. Runner/development/unconfigured hosts report explicit unavailability through `getUpdateStatus()`. ```ts import { getUpdateStatus, checkForUpdates, onUpdateEvent } from '@legendapp/spark/updates'; const subscription = onUpdateEvent(event => { if (event.state === 'error') console.error(event.message); else if ('version' in event) console.log(event.state, event.version); }); const status = await getUpdateStatus(); if (status.available && status.canCheck) { await checkForUpdates({ mode: 'interactive' }); } subscription.remove(); ``` Retain the subscription for the app/UI lifetime; the last line illustrates teardown. Checking resolves when initiated. Progress/completion arrives as typed events: checking/notAvailable, version-bearing available/downloading/downloaded/installing, or error with message. `startUpdates` starts a configured updater. `configureUpdates({ automaticallyChecks?, checkIntervalSeconds? })` changes preferences; an empty object starts the configured updater without changing preferences. Feed/key configuration belongs to the native app and can require rebuilding. Update build numbers must increase. The CLI validates a new build against the feed and earlier local artifact; equal or lower numbers reject. Test signed installation, relaunch, and rejection of older builds with your app and feed. See [update configuration](https://github.com/LegendApp/legend-spark/blob/main/docs/desktop-integrations.md) and [status and limitations](https://legend.so/spark/limitations/). ## [Transfer a local SDK](https://legend.so/spark/distribution/#transfer-a-local-sdk) From the framework checkout: ```sh bun run spark sdk export /path/to/SparkSDK --runtime /path/to/SparkRunner.app ``` The transferable directory contains immutable package archives, checksums, an installer, and optional runtimes. `--runtime` can repeat. A Windows export includes the entire product directory with its DLLs. Exports refuse existing destinations and only publish output after validation. On the recipient, preserve bytes, permissions, and symlinks, then: ```sh cd /path/to/SparkSDK node install.mjs ``` Use the installed CLI command printed by the installer. Installation verifies archives before installing/registering the SDK. Network access is still needed for third-party dependencies; checksums detect corruption but are not publisher signatures. Keep the installed directory/runtime locations stable. Relocate before installation; moving an installed SDK requires reinstalling/refreshing app references. This workflow is useful for current-source testing and does not require public npm publication. ## [Matching SDK runtimes](https://legend.so/spark/distribution/#matching-sdk-runtimes) Use a Runner that matches your SDK and native dependencies. Rebuild or register a matching local Runner after SDK/native changes. Source packing, registration, and export do not publish artifacts. See [SDK transfer](https://github.com/LegendApp/legend-spark/blob/main/docs/sdk-distribution.md). [View Markdown](https://legend.so/spark/distribution.md)[Report a documentation correction](https://github.com/LegendApp/legend-docs/issues/new?title=Docs%3A+Packaging+and+updates&body=Page%3A+https%3A%2F%2Flegend.so%2Fspark%2Fdistribution%0A%0AWhat+is+incorrect+or+missing%3F%0A%0AExpected+behavior+or+supporting+source%3A%0A) --- # Documents and application lifecycle Quit decisions, replayable open requests, recent documents, and owned controllers. Source: https://legend.so/spark/documents/ Legend Spark: Experimental — not for production. These guides cover source SDK APIs; the published preview can differ. [Check release status.](https://legend.so/spark/limitations/) ## [Application lifecycle](https://legend.so/spark/documents/#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. ```ts 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](https://legend.so/spark/documents/#open-requests-and-recent-history) Use `@legendapp/spark/app/documents` for file/URL launch requests and recent documents: ```ts 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](https://legend.so/spark/documents/#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](https://github.com/LegendApp/legend-spark/blob/main/docs/app.md) and [document coordination](https://github.com/LegendApp/legend-spark/blob/main/docs/links-and-documents.md). [View Markdown](https://legend.so/spark/documents.md)[Report a documentation correction](https://github.com/LegendApp/legend-docs/issues/new?title=Docs%3A+Documents+and+application+lifecycle&body=Page%3A+https%3A%2F%2Flegend.so%2Fspark%2Fdocuments%0A%0AWhat+is+incorrect+or+missing%3F%0A%0AExpected+behavior+or+supporting+source%3A%0A) --- # Drag and drop Native drag sources and drop destinations for files, text, URLs, and custom data. Source: https://legend.so/spark/drag-drop/ Legend Spark: Experimental — not for production. These guides cover source SDK APIs; the published preview can differ. [Check release status.](https://legend.so/spark/limitations/) `@legendapp/spark/drag-drop` exports `DragDropView`, `getDragDropAvailability`, and payload/event types. One view can be a source, a destination, or both, and accepts ordinary React Native `ViewProps`. macOS 14+ (Apple Silicon) supported; Windows preview, not yet verified. Native drag interaction and Windows compilation still need acceptance for this API. ## [Sources and destinations](https://legend.so/spark/drag-drop/#sources-and-destinations) ```tsx import { Text } from 'react-native'; import { DragDropView } from '@legendapp/spark/drag-drop'; export function DragExample() { return ( <> console.log(event.accepted, event.operation)} onError={console.error} > Drag this item { console.log({ files, data, operation, x, y }); }} onError={console.error} > Drop here ); } ``` ## [Payloads and negotiation](https://legend.so/spark/drag-drop/#payloads-and-negotiation) `DragPayload` can contain several representations at once: | Field | Value | | --- | --- | | `files` | Array of absolute native paths. Local file URLs in a source are normalized. | | `text` | A string. | | `urls` | Array of absolute URLs. Windows sources allow one URL; multiple URLs reject with `E_UNSUPPORTED_OPTION`. | | `data` | Map of lowercase custom MIME names to UTF-8 strings. The app owns JSON schemas and validation. | A source must contain at least one representation. `text/plain`, `text/uri-list`, and `application/x-spark-drag` are reserved; use the built-in fields for text and URLs. `sourceOperations` and `acceptedOperations` accept `copy`, `move`, and `link`, and each defaults to `['copy']`. Destinations default to `acceptedTypes={['files', 'text', 'urls']}`; custom MIME types require explicit opt-in. The first mutually supported target operation wins. Empty accepted operations or types reject all drops; `disabled` disables both directions. Accepted types determine drop eligibility. A payload may still include coexisting built-in representations, while custom data is restricted to requested custom types. ## [Events](https://legend.so/spark/drag-drop/#events) | Callback | Payload | | --- | --- | | `onDragEnter`, `onDrop` | `DropEvent`: payload fields plus `x`, `y`, and `operation`. | | `onDragOver` | `DragOverEvent`: only `x`, `y`, and `operation`. | | `onDragLeave` | No payload. | | `onDragEnd` | `{ accepted: true, operation: 'copy' | 'move' | 'link' }` or `{ accepted: false, operation: 'none' }`. | Coordinates use logical units from the destination's top-left corner, positive downward. Acceptance describes native negotiation, not successful application persistence. For a move, coordinate persistence and source deletion in application code. ## [Availability and errors](https://legend.so/spark/drag-drop/#availability-and-errors) `getDragDropAvailability()` synchronously reports an unsupported platform or a missing native component. An unavailable host renders an ordinary `View` with the children intact. Remount after repairing the host to retry. `onError` receives `SparkError` for unavailable hosts, malformed native events, and native operation failures; its default is `console.error`. The view adds no error message to the UI. Invalid source/options throw during rendering and can be handled by a React error boundary. Invalid native events report through `onError` without invoking their event callback. See the [drag-and-drop source guide](https://github.com/LegendApp/legend-spark/blob/261e2c337905d501d86f8b38b9036feb4ed746b4/docs/drag-drop.md) and [shared error contracts](https://legend.so/spark/reference/#shared-contracts). [View Markdown](https://legend.so/spark/drag-drop.md)[Report a documentation correction](https://github.com/LegendApp/legend-docs/issues/new?title=Docs%3A+Drag+and+drop&body=Page%3A+https%3A%2F%2Flegend.so%2Fspark%2Fdrag-drop%0A%0AWhat+is+incorrect+or+missing%3F%0A%0AExpected+behavior+or+supporting+source%3A%0A) --- # Examples and validation Explore Kitchen Sink, generate focused apps, and run source and platform checks. Source: https://legend.so/spark/examples/ Legend Spark: Experimental — not for production. These guides cover source SDK APIs; the published preview can differ. [Check release status.](https://legend.so/spark/limitations/) ## [Kitchen Sink](https://legend.so/spark/examples/#kitchen-sink) From the current source checkout: ```sh bun run kitchen-sink ``` After installing/preparing the source SDK, normal development starts the managed session; it does not compile a native runtime. A matching registered Runner is required. If needed, build from the checked-in app: ```sh cd examples/kitchen-sink bun run rebuild:macos bun run macos # On Windows: # bun run rebuild:windows # bun run windows ``` Kitchen Sink demonstrates windows/geometry, menus/shortcuts, files/dialogs, streaming/Trash, settings, clipboard, notifications/system APIs, audio, and native controls. Progress, results, and errors appear inline with detailed logs. Disposable filesystem demos create/recycle test data. `bun run kitchen-sink:prepare /absolute/path/to/KitchenSinkCheck` packs the SDK and creates a separate copied consumer for integration testing. It preserves that consumer's generated identity/configuration rather than changing the checked-in app. ## [Smaller applications](https://legend.so/spark/examples/#smaller-applications) Create a focused app with npm: ```sh npm create @legendapp/spark@next MyNotes -- --example notes-lite npm create @legendapp/spark@next MyMusic -- --example music-lite npm create @legendapp/spark@next MyDiff -- --example diff-lite npm create @legendapp/spark@next MyEditor -- --example document-editor ``` Then run the generated app, for example: ```sh cd MyNotes npm run macos ``` For local SDK development, [prepare the source SDK](https://legend.so/spark/getting-started/#contributing-build-spark-from-source) and run `bun run spark create /absolute/path/to/MyNotes --example notes-lite` from its checkout. Notes Lite covers local notes, search, import/export, recovery, and session restoration. Music Lite covers playback/queue ownership. Diff Lite covers text comparison. Document Editor covers file operations, menus, document requests, and desktop lifecycle. These examples are distinct from the full Legend Apps products. The [helper example](https://github.com/LegendApp/legend-spark/tree/main/examples/sidecar) demonstrates an app-supplied executable without a Node runtime in Hermes. The universal Settings starter uses native controls plus platform adapters. ## [Source checks](https://legend.so/spark/examples/#source-checks) From the SDK checkout: ```sh bun run typecheck bun run test bun run test:templates ``` `bun run test` uses Vitest, not Bun's test runner. Source checks validate contract behavior, export policy, lifecycle transitions, and tooling; template checks create real consumers. Packed consumer checks separately validate the archive's dependency graph and declarations. ## [Native and platform checks](https://legend.so/spark/examples/#native-and-platform-checks) Native suites need their toolchains and disposable consumer projects. Read prerequisites before running them. Relevant commands include: | Command | Purpose | | --- | --- | | `bun run test:native` | Native API probes | | `bun run test:ui` | Packed native UI consumer | | `bun run test:platform --platform ` | Shared contract catalog on a target | | `bun run test:windows` | Windows Runner → Fast Refresh → added module → custom build | | `bun run test:windows:prepare` | Generation/bundles without Windows native execution | | `bun run test:runtimes:all` | Runner/dev/release background runtimes and pruning | | `bun run test:universal` / `bun run test:universal:dev` | Universal generation/bundles and shared sessions | | `bun run test:add-desktop` | Existing Expo app preservation/composition | Run your app on each target OS and architecture to check native behavior as well as generation and bundling. See the [platform testing guide](https://github.com/LegendApp/legend-spark/blob/main/docs/platform-testing.md), [manual testing checklist](https://github.com/LegendApp/legend-spark/blob/main/docs/desktop-manual-acceptance.md), and [platform limits](https://legend.so/spark/limitations/). [View Markdown](https://legend.so/spark/examples.md)[Report a documentation correction](https://github.com/LegendApp/legend-docs/issues/new?title=Docs%3A+Examples+and+validation&body=Page%3A+https%3A%2F%2Flegend.so%2Fspark%2Fexamples%0A%0AWhat+is+incorrect+or+missing%3F%0A%0AExpected+behavior+or+supporting+source%3A%0A) --- # Files and dialogs Explicit dialog results, native paths, byte streams, watches, and scans. Source: https://legend.so/spark/files/ Legend Spark: Experimental — not for production. These guides cover source SDK APIs; the published preview can differ. [Check release status.](https://legend.so/spark/limitations/) ## [Dialogs](https://legend.so/spark/files/#dialogs) `@legendapp/spark/dialogs` contains open/save panels, `showMessage`, and `confirm`. File panels use `defaultPath` and labeled extension filters: ```ts import { openFileDialog } from '@legendapp/spark/dialogs'; import { readText } from '@legendapp/spark/files'; const result = await openFileDialog({ windowId: 'main', selection: 'files', multiple: false, defaultPath: '/absolute/path/to/documents', filters: [{ name: 'Text documents', extensions: ['txt', 'md'] }], }); if (!result.canceled) { const text = await readText(result.paths[0]); console.log(text); } ``` An owner is optional; a supplied `windowId` must identify a live window. Open returns `{ canceled: true }` or `{ canceled: false, paths }`. Save returns the same cancellation discriminant with one `path`; it can accept `defaultName`. Extensions omit dots; MIME types/UTIs are unsupported. Windows displays labeled groups; macOS combines extensions and ignores group labels. Mixed file/directory selection is explicitly macOS-specific. Message buttons have stable IDs and labels. `showMessage` returns `buttonId` (null on host dismissal), and includes `checked` only when a checkbox was supplied. `confirm` returns a boolean. Missing modules, bad options, and native failures are errors rather than fabricated cancellation. ## [Native file IO](https://legend.so/spark/files/#native-file-io) `@legendapp/spark/files` owns asynchronous desktop operations. Paths are absolute native paths or local `file://` URLs decoded once. Nonlocal file URLs, query strings, and fragments reject. Bytes use `Uint8Array`. Use `getDirectory('data')` for project-scoped storage, `list` for immediate children, `stat` for metadata, `readText`/`writeText` for UTF-8, and `readBytes`/`writeBytes` for binary content. `stat` describes a symlink itself; invalid UTF-8 and permission failures reject. `copy(source, destination, { overwrite? })` and `move` reject existing destinations by default. Explicit overwrite replaces them. Directory copy is recursive; cross-volume move may copy then delete. Neither is a transaction across multiple files. Whole-file writes replace atomically on supported backends. `writeTextIfUnchanged` compares observed text and returns `{ written: false }` on conflict; it is not a cross-process compare-and-swap. `remove` defaults to nonrecursive removal; missing paths succeed. `trash` rejects if recycling is unavailable and never falls back to permanent deletion. `revealInFileManager` is distinct from `/links` opening a file in its associated application. ## [Bounded streams and handles](https://legend.so/spark/files/#bounded-streams-and-handles) ```ts import { getDirectory, readChunks } from '@legendapp/spark/files'; const path = `${await getDirectory('data')}/large.bin`; for await (const bytes of readChunks(path)) { console.log(bytes.byteLength); } ``` `readChunks`/`writeChunks` avoid loading an entire file. Iteration closes ownership on completion, error, cancellation, or an early loop exit. `openFile` exposes positional read/write, `flush`, and `close`. Streaming/positional writes can be partial; inspect the [streaming contract](https://github.com/LegendApp/legend-spark/blob/main/docs/file-streams.md). Close stops accepting new work, joins pending cleanup, and permits retry after native cleanup failure. File cleanup errors can expose retained handles rather than losing ownership. ## [Watching and scanning](https://legend.so/spark/files/#watching-and-scanning) `watch(path, listener, { recursive })` resolves an async registration. Notifications invalidate cached data; re-read the path. They can coalesce and do not form an exact add/delete journal. Recursive watches require a directory. Await removal. `scanFiles(paths, options)` is operation-scoped and currently macOS-only. Batches/progress belong to that invocation; concurrent scans do not share singleton callbacks. Results include totals and partial traversal errors. Abort stops between filesystem operations and rejects with `E_ABORTED` after native work stops; a pending OS call is not interrupted. Check `getFileScanAvailability()` when presenting this feature. All file helpers, scanning, and watching share `/files`. See [the complete file guide](https://github.com/LegendApp/legend-spark/blob/main/docs/files.md). [View Markdown](https://legend.so/spark/files.md)[Report a documentation correction](https://github.com/LegendApp/legend-docs/issues/new?title=Docs%3A+Files+and+dialogs&body=Page%3A+https%3A%2F%2Flegend.so%2Fspark%2Ffiles%0A%0AWhat+is+incorrect+or+missing%3F%0A%0AExpected+behavior+or+supporting+source%3A%0A) --- # Getting started Create a macOS app from npm, edit it with Fast Refresh, and build a standalone app. Source: https://legend.so/spark/getting-started/ Legend Spark: Experimental — not for production. These guides cover source SDK APIs; the published preview can differ. [Check release status.](https://legend.so/spark/limitations/) Experimental Spark is experimental and not ready for production use. See [status and limitations](https://legend.so/spark/limitations/). ## [Prerequisites](https://legend.so/spark/getting-started/#prerequisites) Use macOS **14 or newer** on **Apple Silicon** with Node **24.19.0 or newer**. npm comes with Node; pnpm, Yarn, and Bun are also supported package managers. Bun is optional for app development. Creating an app and running it with Spark Runner does not require Xcode or CocoaPods. You need those tools when compiling a standalone app or a custom native development build. ## [Create an app](https://legend.so/spark/getting-started/#create-an-app) Create an app with npm: ```sh npm create @legendapp/spark@next MyApp cd MyApp ``` The command runs `@legendapp/create-spark`, which creates the app and installs its dependencies. `@legendapp/spark` includes the `spark` CLI and the public APIs. Other package managers: ```sh bun create @legendapp/spark@next MyApp pnpm create @legendapp/spark@next MyApp yarn create @legendapp/spark@next MyApp # Yarn 2+ ``` For Yarn Classic (1.x), use npm to run the tagged creator and select Yarn for installation: ```sh npm create @legendapp/spark@next MyApp -- --package-manager yarn ``` To choose a package manager, pass `--package-manager npm|pnpm|yarn|bun`. For example: ```sh npm create @legendapp/spark@next MyApp -- --package-manager pnpm ``` The chosen manager must be installed. The commands below use npm; use your chosen manager's `run` command instead if needed. Keep the generated lockfile and `spark-packages/` archives in version control so other machines can reinstall the same inputs. ## [Run and edit](https://legend.so/spark/getting-started/#run-and-edit) From the generated app: ```sh npm run macos ``` On the first launch, Spark downloads and verifies the matching macOS ARM64 Spark Runner. This requires an internet connection. Later sessions reuse the downloaded Runner. Expo CLI starts Metro and opens your app in the Runner. Open `App.tsx`, change the `Hello, Legend Spark` text, and save. JavaScript changes appear through Fast Refresh. Extra native dependencies or native configuration can require a [custom development build](https://legend.so/spark/development/#native-changes). The generated app includes these scripts: | Command | Purpose | | --- | --- | | `npm run macos`, `npm run dev`, `npm start` | Start the development session | | `npm run build` | Compile a standalone macOS app | | `npm run package` | Sign and notarize a distribution archive | | `npm run doctor` | Check native build prerequisites and dependency pins | ## [Build a standalone app](https://legend.so/spark/getting-started/#build-a-standalone-app) Install full Xcode, complete its first-launch setup, and select it in the Xcode command-line tools settings. Install CocoaPods and ensure `pod` is on your PATH. Command Line Tools alone cannot compile the app. Stop the development session with Ctrl+C, then run: ```sh npm run build npx --no-install spark open ``` The build prints the location of an ad-hoc-signed `.app` with embedded JavaScript. It runs without Metro. `spark open` launches the last standalone build. To prepare a signed, notarized archive with your Developer ID credentials, use `npm run package`. See [packaging and updates](https://legend.so/spark/distribution/) for signing setup and distribution. ## [Contributing: build Spark from source](https://legend.so/spark/getting-started/#contributing-build-spark-from-source) For framework development, install Git and the checkout's pinned Bun **1.3.14**, in addition to Node and the native build tools above: ```sh git clone https://github.com/LegendApp/legend-spark.git cd legend-spark bun install bun run spark sdk pack bun run spark sdk build-runner bun run spark create /absolute/path/to/MyApp cd /absolute/path/to/MyApp npm run macos ``` `sdk pack` creates local SDK archives and registers their manifest. `sdk build-runner` compiles and registers a matching Runner. To reuse an existing matching Runner, run `bun run spark sdk register /absolute/path/to/SparkRunner.app` from the checkout instead of building it. Keep the registered Runner app at that path. For Windows 11 source development, follow the [Windows guide](https://github.com/LegendApp/legend-spark/blob/main/docs/windows-slice.md). Use `--platform windows` with `sdk pack`, `sdk build-runner`, and `create`, then run the generated `windows` script. Windows native compilation and runtime behavior still need validation; production distribution is unsupported. See [SDK iteration](https://legend.so/spark/development/#sdk-iteration-and-diagnostics) for refreshing local apps after source changes. [View Markdown](https://legend.so/spark/getting-started.md)[Report a documentation correction](https://github.com/LegendApp/legend-docs/issues/new?title=Docs%3A+Getting+started&body=Page%3A+https%3A%2F%2Flegend.so%2Fspark%2Fgetting-started%0A%0AWhat+is+incorrect+or+missing%3F%0A%0AExpected+behavior+or+supporting+source%3A%0A) --- # Legend Spark React Native, meet the desktop. Source: https://legend.so/spark/ ![](https://legend.so/assets/legend-spark.png)Legend SparkExperimental ## React Native, meet the desktop. Spark gives React Native apps real windows, menus, dialogs, and files on the Mac. Start in a prebuilt runner with Fast Refresh, then ship a standalone `.app`. [Get started](https://legend.so/spark/getting-started/) [GitHub](https://github.com/LegendApp/legend-spark) App.tsx ``` import { AppRegistry, Text } from 'react-native' import { createMenu } from '@legendapp/spark/menus' import { openWindow } from '@legendapp/spark/windows' AppRegistry.registerComponent('Inspector', () => () => Inspector) createMenu({ id: 'editor', items: [{ type: 'submenu', id: 'view', label: 'View', items: [{ type: 'action', id: 'inspector', label: 'Show Inspector', shortcut: 'CmdOrCtrl+I' }], }], onAction: ({ itemId }) => { if (itemId === 'inspector') { // A second native window, rendering a React Native root openWindow({ id: 'inspector', component: 'Inspector', title: 'Inspector', size: { width: 480, height: 640 }, }) } }, }).catch(console.error) ``` Current source API · [see Getting started](https://legend.so/spark/getting-started/) ## One package, the whole desktop. Import each capability from `@legendapp/spark`. Typed and async, with explicit ownership and cleanup. [All capabilities](https://legend.so/spark/reference/) **Windows`/windows`** Named windows, displays, geometry, close guards, and native macOS chrome. **Menus`/menus`** The app menu bar, context menus, tray items, and Dock menus. **Shortcuts`/shortcuts`** Local and global accelerators, with command routing. **Files and dialogs`/files`** Open and save panels, atomic writes, streams, watching, and Trash. **Settings`/settings`** JSON settings, Legend State persistence, and secure storage. **Updates`/updates`** Native whole-app updates for the apps you ship. ## One project, three ways to run it. Move from a shared runner to your own build only when the project needs it. Spark checks native requirements before loading JavaScript. 1. 01 / SPARK RUNNER ### Start without Xcode A prebuilt development app. Create a project, run it, and edit App.tsx with Fast Refresh. ``` $ npm create @legendapp/spark@next MyApp$ cd MyApp$ npm run macos ``` 2. 02 / CUSTOM DEVELOPMENT BUILD ### Add native when you need it Extra native modules or helper executables need an app-specific build. JavaScript changes still use Fast Refresh. ``` $ npx --no-install spark build --dev --platform macos ``` 3. 03 / STANDALONE APP ### Ship a real Mac app Build a .app with embedded JavaScript, then sign and notarize it with your own Developer ID. ``` $ npm run build$ npm run package ``` ## No browser inside. React Native all the way downScreens are React Native views and your JavaScript runs in Hermes. Node runs the tooling and is never bundled into the app. The Expo workflow you knowExpo Desktop generates the native host and Expo CLI owns Metro, logs, and reload. Spark adds desktop APIs and runtime selection. ## Where it stands Spark is experimental and not ready for production; APIs can still change. macOS 14+ on Apple Silicon is the primary target. Intel Mac builds and Windows development exist in source, with native acceptance pending. The `@next` preview uses matching SDK archives and downloads the macOS Apple Silicon Spark Runner on first launch. Signed distribution acceptance remains pending. [Status and limitations](https://legend.so/spark/limitations/) --- # Desktop and shared integrations Clipboard, notifications, system APIs, processes, databases, WebView, audio, and auth. Source: https://legend.so/spark/integrations/ Legend Spark: Experimental — not for production. These guides cover source SDK APIs; the published preview can differ. [Check release status.](https://legend.so/spark/limitations/) ## [Clipboard, links, and drag/drop](https://legend.so/spark/integrations/#clipboard-links-and-dragdrop) `/clipboard` retains selected Expo-shaped `getStringAsync`, `setStringAsync`, and `hasStringAsync` methods. `setStringAsync` returns the actual boolean write result. Rich `readClipboard`/`writeClipboard` use owned payloads; file lists are separate from text/image alternatives. Raw format identifiers come from `getClipboardFormats`. `/links` supplies `openURL`, `canOpenURL`, `getInitialURL`, and `addEventListener('url', listener)`. Desktop `openPath` opens a native file with its associated application. File/URL launch replay and recent history live under [/app/documents](https://legend.so/spark/documents/). `/drag-drop` handles generic files/text/URLs/custom MIME payloads with consumer-facing events and explicit operations/coordinates. Application-specific music/track models do not belong in the SDK. Use availability and `onError` rather than assuming every import has a native backend. See [Drag and drop](https://legend.so/spark/drag-drop/) for sources, destinations, and event contracts. ## [Local notifications](https://legend.so/spark/integrations/#local-notifications) ```ts import { requestNotificationPermission, showNotification } from '@legendapp/spark/notifications'; const permission = await requestNotificationPermission(); if (permission.granted) { await showNotification({ id: 'import-complete', content: { title: 'Import complete', body: 'Your files are ready.', sound: 'message', actions: [{ id: 'view', label: 'View files' }], }, }); } ``` Request permission from an explicit user action. Windows reads OS settings without an in-app permission prompt. The OS may accept a notification without displaying it. `showNotification` is immediate; `scheduleNotification` adds `{ type: 'delay', delaySeconds }`. Content supports silent/default sound or portable `message`, `mail`, `reminder`, `call`, and `error` tones. One to four action buttons report their IDs through responses. macOS banners show the first two; Notification Center can show the rest. `onNotificationResponse` installs live delivery and drains retained launch responses. Await setup; the returned subscription removes synchronously. Responses identify the notification and `open`, `dismiss`, or a custom action. New subscriptions can replay retained responses. `cancelNotification`/`cancelAllNotifications` remove pending delivery. `dismissNotification`/`dismissAllNotifications` remove already delivered notifications. List APIs distinguish those sets. These are local notifications, not push tokens or background tasks. ## [System integration](https://legend.so/spark/integrations/#system-integration) `/system` supplies system information, events, login startup, badges, attention, sleep prevention, and explicit Dock/taskbar menu registration. Events are invalidations: query `getSystemInfo()` again for current values. `requestAttention` and `preventSleep` return owned async registrations. Remove them when the application no longer needs the effect. Unsupported options reject; menu trees follow [surface restrictions](https://legend.so/spark/menus/). ## [Processes and helper executables](https://legend.so/spark/integrations/#processes-and-helper-executables) ```ts import { runCommand } from '@legendapp/spark/processes'; const result = await runCommand({ target: { type: 'command', name: 'git' }, args: ['--version'], timeoutMs: 10_000, }); if (result.exit.type === 'exited' && result.exit.code === 0) { console.log(new TextDecoder().decode(result.stdout)); } ``` Targets discriminate PATH commands, absolute executables, and configured helper references. Arguments bypass a shell. Input/output use strings or byte arrays; output callbacks carry `Uint8Array` with arbitrary chunk boundaries. Results retain stdout/stderr, exit/termination, timeout, abort, and truncation. Nonzero exit is a result; an operational failure rejects. `spawn` returns an owned handle with write, closeInput, terminate, and exited. Await writes for backpressure. Termination joins concurrent calls and waits for the process tree/output streams. It can be retried after native cleanup failure. Helpers are target-specific executables supplied by the app under `desktop.config.json.helpers`. Spark packages their assets/dependencies but does not compile them or supply Node. Helpers require a custom binary. Keep shared process ownership in an application service, rather than each window. They are not persistent OS services; abrupt macOS app death does not guarantee cleanup. See [helper processes](https://github.com/LegendApp/legend-spark/blob/main/docs/sidecars.md). ## [SQLite](https://legend.so/spark/integrations/#sqlite) ```ts import { openDatabase } from '@legendapp/spark/sqlite'; const database = await openDatabase('notes.sqlite'); try { await database.transaction(async tx => { await tx.run('CREATE TABLE IF NOT EXISTS notes (title TEXT NOT NULL)'); await tx.run('INSERT INTO notes (title) VALUES (?)', ['Hello']); }); const note = await database.getFirst('SELECT title FROM notes LIMIT 1'); console.log(note); } finally { await database.close(); } ``` The project-scoped filename must be a simple `.sqlite` name. Spark owns `Database`, `SqlExecutor`, row/value types, and run results; it does not expose OP-SQLite's raw DB. Use `tx` inside transactions. Success commits, rejection rolls back; direct connection operations during a transaction and nested transactions reject. Blobs are `Uint8Array`; SQL NULL is null. Default integer mode rejects unsafe integer reads. `{ integers: 'text' }` permits out-of-range values as text, but an already rounded native number cannot recover exact digits. Use SQL `CAST(column AS TEXT)` when exact large integers matter. Close waits for accepted work and blocks new work. ## [WebView](https://legend.so/spark/integrations/#webview) `/webview` exports an owned `WebView` component, props, events, and ref subset. Source is a URI with optional headers **or** inline HTML with optional `baseUri`. Normal React Native view props are supported; document content comes from source, not children. Refs request reload/back/forward, string messaging, or script injection; they do not promise navigation completion. Load/HTTP errors and navigation/message payloads are typed. Navigation interception is synchronous where emitted and is not a network security boundary. Consult platform limits for origins, headers, scripts, cookies, and popup behavior; this is not the whole `react-native-webview` API. ## [Audio and browser authentication](https://legend.so/spark/integrations/#audio-and-browser-authentication) `createAudioPlayer(source, options?)` waits for readiness and returns async play/pause/seek/volume/metadata/status commands. Positions use seconds; creation supports timeout/abort. `useAudioPlayer` returns loading/ready/error state, with a player only when ready. Removal owns disposal; hooks handle replacement and late completion. `createMediaSession` lets desktop/web publish controls for an external engine. One session owns system controls; stale handles cannot clear a newer session. Command callbacks request operations rather than confirming playback. Standalone external-engine sessions are unsupported on mobile, where Expo Audio owns its player controls. Browser playback may require a gesture. See [audio](https://github.com/LegendApp/legend-spark/blob/main/docs/audio.md). `/auth-session` provides prepared state/redirect sessions, browser callback transport, and explicit dismissal. Success, cancellation, dismissal, and timeout remain distinct outcomes. Provider SDKs, token exchange, and credential policy belong to the app. This is not complete `expo-auth-session` compatibility. See [authentication](https://github.com/LegendApp/legend-spark/blob/main/docs/auth-session.md). ## [Independent Hermes runtimes](https://legend.so/spark/integrations/#independent-hermes-runtimes) Use `@react-native-runtimes/core` directly for background work in independent Hermes heaps inside the application process. Serialization, cleanup, native-module limits, and production reachability follow its integration guide. It is not Node or a persistent worker service. See [Runtimes](https://github.com/LegendApp/legend-spark/blob/main/docs/runtimes.md). [View Markdown](https://legend.so/spark/integrations.md)[Report a documentation correction](https://github.com/LegendApp/legend-docs/issues/new?title=Docs%3A+Desktop+and+shared+integrations&body=Page%3A+https%3A%2F%2Flegend.so%2Fspark%2Fintegrations%0A%0AWhat+is+incorrect+or+missing%3F%0A%0AExpected+behavior+or+supporting+source%3A%0A) --- # Keyboard shortcuts Local and global accelerators, command routing, customizable bindings, and keyboard events. Source: https://legend.so/spark/keyboard-shortcuts/ Legend Spark: Experimental — not for production. These guides cover source SDK APIs; the published preview can differ. [Check release status.](https://legend.so/spark/limitations/) Choose a shortcut surface according to its owner: | Import | Purpose | | --- | --- | | `@legendapp/spark/shortcuts` | Shortcuts in the focused application, optionally scoped to a window. | | `@legendapp/spark/global-shortcuts` | System-wide shortcuts, including when the app is unfocused. | | `@legendapp/spark/shortcuts/commands` | Named commands, routing, capture controls, and persisted bindings. | | `@legendapp/spark/shortcuts/keyboard` | Low-level observation of keydown and keyup. | macOS 14+ (Apple Silicon) supported; Windows preview, not yet verified. Local and global registration have desktop backends; the command router and low-level keyboard backend currently require macOS. Native key dispatch, layouts, media keys, and focus transitions still need host acceptance. ## [Accelerator strings](https://legend.so/spark/keyboard-shortcuts/#accelerator-strings) Menus and registered shortcuts share named accelerators such as `CmdOrCtrl+S`, `Alt+Left`, `Shift+Up`, and `F5`. `CmdOrCtrl` means Command on macOS and Control on Windows. Character shortcuts require a modifier on these surfaces. Use `Plus` for the plus key, for example `CmdOrCtrl+Plus`. Commands additionally allow unmodified character keys, `Home`, `End`, `PageUp`, `PageDown`, `MediaPlayPause`, `MediaNext`, and `MediaPrevious`, with explicit `Fn` and `CapsLock` modifiers. Other shortcut surfaces reject those extensions. Bindings use named strings; physical codes belong to low-level events. ## [Local and global registrations](https://legend.so/spark/keyboard-shortcuts/#local-and-global-registrations) ```ts import { registerShortcut } from '@legendapp/spark/shortcuts'; import { registerGlobalShortcut } from '@legendapp/spark/global-shortcuts'; const local = await registerShortcut('CmdOrCtrl+S', () => { console.log('Save requested'); }, { windowId: 'main' }); const global = await registerGlobalShortcut('CmdOrCtrl+Shift+K', () => { console.log('Global shortcut requested'); }); // When the owner ends: await local.remove(); await global.remove(); ``` Local shortcuts are consumed before insertion into text fields. Options are `windowId` and `repeat`; repeat defaults to `false`. Global options contain only `repeat`. Windows supports global repeat; requesting `repeat: true` on macOS rejects `E_UNSUPPORTED_OPTION`. Conflicting or reserved global shortcuts reject with `E_BUSY`. `getShortcutAvailability()` and `getGlobalShortcutAvailability()` report host/module availability from their respective imports. Registration failures reject. Await removal and retain a failed handle for cleanup retry; removal stops JavaScript callbacks immediately. To display a shortcut in an application menu, use the menu item's `shortcut` field; see [menus](https://legend.so/spark/menus/). ## [Named commands and routing](https://legend.so/spark/keyboard-shortcuts/#named-commands-and-routing) ```ts import { createHotkeyRouter } from '@legendapp/spark/shortcuts/commands'; const definitions = [ { id: 'save', title: 'Save', defaultBindings: ['CmdOrCtrl+S'] }, ] as const; const router = createHotkeyRouter(); const registration = await router.register({ definitions, scope: { kind: 'window', windowId: 'editor' }, handlers: { save: () => { console.log('Save requested'); } }, }); await registration.setEnabled(false); await registration.setEnabled(true); const suspension = await router.suspend({ kind: 'window', windowId: 'editor' }); await suspension.remove(); await registration.remove(); ``` `defaultBindings` and per-command `bindings` overrides are arrays. An empty override disables a command; an omitted override uses its defaults. Registrations snapshot their configuration. Higher priority runs first: defaults are 100 for window scope and 0 for application scope. Equal priorities favor the newest registration. A synchronous handler returning `false` lets dispatch continue to the next Spark handler. Other synchronous results stop routing. Native consumption is decided before JavaScript runs, so returning `false` cannot send the key back to AppKit. Launch asynchronous work explicitly and handle its errors. Only definitions with `repeat: true` run on repeated keydowns; `allowExtraModifiers` opts into subset modifier matching. The native event's window ID determines ownership. Window changes reset pressed-key state; `setActiveWindowId` can also reset that state on app focus changes, but does not override native event ownership. `enabled` is a boolean. Await `setEnabled` for native acknowledgment. `router.suspend(scope?)` returns an async registration; await its `remove()` to resume. Window suspension also blocks application handlers in that window. The final command registration releases shared native observers and consumption rules. Setup, updates, and cleanup reject on failure, and cleanup can be retried on the same handle. ### [React ownership and capture](https://legend.so/spark/keyboard-shortcuts/#react-ownership-and-capture) `useRoutedHotkeys({ router, definitions, bindings, handlers, ... })` returns `loading`, `ready` with the registration, or `error` state. It uses current handlers and enabled state without replacing the registration for callback identity changes, and removes late registrations after unmount. `onError` reports setup/update failures; `onCleanupError(error, registration)` exposes failed cleanup for retry. `useHotkeySuspension({ active, router, scope?, onError?, onCleanupError? })` owns suspension for an effect lifetime. Both hooks share the commands import. `HotkeyCapture` takes `value`, `onChange`, and optional capture/error callbacks. It owns native capture only while recording, commits a named accelerator on key release, and cancels on Escape. Capture temporarily consumes monitored keys. `HotkeyBindingsSettingsContent` and `HotkeyBindingsSettingsPage` edit arrays of bindings through `definitions`, `values`, and `onChange(id, values)`; `maxBindingsPerCommand` can limit the number per command. ## [Persisting bindings](https://legend.so/spark/keyboard-shortcuts/#persisting-bindings) `createHotkeyStore({ definitions, path, maxBindingsPerCommand?, storage?, debounceMs? })` comes from the commands import. Await the factory before using its Legend State `value$`, `error$`, `flush()`, and `close()`. The parent directory must exist; follow [observable persistence](https://legend.so/spark/settings/#observable-persistence). The file format is: ```json { "version": 1, "bindings": { "save": ["CmdOrCtrl+S"] } } ``` Serialization preserves portable `CmdOrCtrl`. Missing commands acquire defaults, removed IDs are discarded, and empty arrays stay empty. Invalid versions, scalar bindings, or malformed accelerators reject and preserve existing file bytes. A nonnegative integer `maxBindingsPerCommand` keeps the first N bindings. Conflicts remain visible through `getHotkeyBindingConflicts` and the settings UI rather than silently replacing another command. Await `flush()` to persist the current snapshot and `close()` when its owner ends. The store persists bindings; supply them to a router registration or `useRoutedHotkeys` to apply them. ## [Low-level keyboard events](https://legend.so/spark/keyboard-shortcuts/#low-level-keyboard-events) ```ts import { addKeyboardListener } from '@legendapp/spark/shortcuts/keyboard'; const listener = await addKeyboardListener('down', event => { console.log(event.key, event.keyCode, event.consumed); }, { windowIds: ['editor'] }); await listener.remove(); ``` Use `'down'` or `'up'`. Events include `windowId` (or `null`), a layout-derived unmodified `key`, physical `keyCode`, a numeric `modifiers` mask, and `repeated`, `consumed`, and `captured` booleans. Window filtering applies to both event kinds. `KeyCodes`, `KeyText`, `hasModifier`, and `createModifierMask` are exported from the same path. Callbacks observe the native decision; their return values never consume a key. Native matching does not wait for JavaScript. `getKeyboardAvailability()` checks host/module support; registration explicitly rejects on unsupported hosts or a missing module. Removing the last registration stops monitoring and releases bridge observers. Failed removal can be retried. See the [commands source guide](https://github.com/LegendApp/legend-spark/blob/261e2c337905d501d86f8b38b9036feb4ed746b4/docs/commands.md) for routing and persistence details. [View Markdown](https://legend.so/spark/keyboard-shortcuts.md)[Report a documentation correction](https://github.com/LegendApp/legend-docs/issues/new?title=Docs%3A+Keyboard+shortcuts&body=Page%3A+https%3A%2F%2Flegend.so%2Fspark%2Fkeyboard-shortcuts%0A%0AWhat+is+incorrect+or+missing%3F%0A%0AExpected+behavior+or+supporting+source%3A%0A) --- # Status and limitations Platform support, feature restrictions, and distribution limits. Source: https://legend.so/spark/limitations/ Legend Spark: Experimental — not for production. These guides cover source SDK APIs; the published preview can differ. [Check release status.](https://legend.so/spark/limitations/) Spark is experimental and not ready for production use. APIs, configuration, and native implementations can change. macOS 14+ on Apple Silicon is the primary target. ## [What works where](https://legend.so/spark/limitations/#what-works-where) | Target | Available workflow | Limits | | --- | --- | --- | | macOS 14+, Apple Silicon | Spark Runner, custom development builds, standalone apps, signing/notarization, and whole-app updates | Experimental; check each feature's availability and test it in your app | | macOS 14+, Intel x64 | Source tooling targets builds, Runner registration/downloads, and packaging | Native compilation, runtime behavior, and signed distribution still need validation; the published Runner is ARM64 only | | Windows 11, x64/ARM64 | Local source Runner and custom development builds | Native compilation and runtime behavior still need validation; no hosted Runner or production packaging workflow | | iOS / Android / web | Expo workflows and selected shared API/UI adapters | Desktop windows, files, menus, and processes are not universal capabilities | | Linux / Mac App Store | No supported workflow | Unsupported | ## [Feature restrictions](https://legend.so/spark/limitations/#feature-restrictions) - Common UI controls use different platform implementations. Windows `SegmentedControl` is unsupported. - Search, sidebar, split-view, settings-window composition, and SF Symbols are macOS capabilities. `GlassView`'s native effect requires macOS 26. - Command routing and low-level keyboard APIs require macOS. The native Codex supervisor is also macOS-only. - Standalone media sessions for an external playback engine are unsupported on mobile. - Secure storage is unavailable on web. Check feature-specific availability and permissions before offering a capability. See [native UI](https://legend.so/spark/ui/), [integrations](https://legend.so/spark/integrations/), and [known Windows issues](https://github.com/LegendApp/legend-spark/blob/main/docs/windows-issues.md) for details. ## [Standalone apps and updates](https://legend.so/spark/limitations/#standalone-apps-and-updates) macOS build, Developer ID signing, notarization, and whole-app update tooling are available. Distribution and updates remain experimental: test installation, startup, update/relaunch, and rejection of older builds with your signed app and update feed. See [packaging and updates](https://legend.so/spark/distribution/). Windows production builds, signing, and MSIX packaging are unsupported. ## [Architecture limits](https://legend.so/spark/limitations/#architecture-limits) - Application JavaScript runs in Hermes. Node built-ins and Electron main-process APIs are unavailable. - Helpers are app-supplied, target-specific executables requiring a custom binary. They are not persistent OS services. - Spark SQLite/WebView/audio/UI contracts expose supported subsets, not complete backend APIs. - Expo Router integration, declarative route-based window navigation, and a broad universal UI catalog are not supported. `createWindowsNavigator` provides named-window component coordination. - Frame → Spark migration requires rebuilding matching native runtimes. Old prototype data, credentials, and OS registrations are not automatically migrated. See [migration](https://legend.so/spark/migration/). [View Markdown](https://legend.so/spark/limitations.md)[Report a documentation correction](https://github.com/LegendApp/legend-docs/issues/new?title=Docs%3A+Status+and+limitations&body=Page%3A+https%3A%2F%2Flegend.so%2Fspark%2Flimitations%0A%0AWhat+is+incorrect+or+missing%3F%0A%0AExpected+behavior+or+supporting+source%3A%0A) --- # Menus and shortcuts Owned native menus, portable accelerators, command routing, and tray items. Source: https://legend.so/spark/menus/ Legend Spark: Experimental — not for production. These guides cover source SDK APIs; the published preview can differ. [Check release status.](https://legend.so/spark/limitations/) Spark menu items discriminate `type`, use stable IDs, and share label/disabled/checked semantics. Every surface accepts its own subset; shared types do not imply all native surfaces support submenus, roles, icons, or sliders. ## [Application menus](https://legend.so/spark/menus/#application-menus) ```ts import { createMenu } from '@legendapp/spark/menus'; const menu = await createMenu({ id: 'editor', items: [{ type: 'submenu', id: 'file', label: 'File', target: { menu: 'file' }, items: [{ type: 'action', id: 'open', label: 'Open...', shortcut: 'CmdOrCtrl+O' }], }], onAction: event => { console.log(event.itemId); }, }); await menu.update({ items: [] }); await menu.remove(); ``` Top-level items are submenus. Contributions merge by IDs, never translated labels. Later owners take precedence; removal restores earlier contributions/native items. Updates replace the owner's contribution. Native publication is acknowledged before create/update resolves. Failed publication retains the previous state; failed removal retains retryable ownership. Targets bind native root menus, IDs, or macOS semantic roles. Role items dispatch through native responders; ordinary actions targeting roles dispatch to JavaScript. `useMenu` owns the same lifetime and exposes loading/ready/error state plus cleanup-error callbacks. Keep unchanged item structure stable; callback changes do not require republishing. ## [Surface support](https://legend.so/spark/menus/#surface-support) | Surface | Supported item shapes | Limits | | --- | --- | --- | | App menu | Recursive actions, checkboxes, separators, submenus; macOS roles | Windows role/icon requests reject | | Context menu | Flat actions, checkboxes, separators | No submenus, roles, icons, shortcuts, or sliders | | Tray menu | Actions, checkboxes, separators, recursive submenus | No roles, icons, shortcuts, or sliders | | macOS toolbar popup | Actions, checkboxes, separators, numeric sliders | No submenus, roles, or shortcuts | | macOS Dock | Recursive actions, checkboxes, separators, submenus | Restricted presentation/targeting fields | | Windows taskbar | Flat actions and checkboxes | Disabled entries omitted; restricted fields | `showContextMenu({ windowId, position, items })` requires an explicit owner. Position is logical top-left **content** coordinates, rather than display/outer-frame coordinates. Cancellation returns `{ canceled: true }`; selection returns `{ canceled: false, itemId }`. A concurrent popup rejects `E_BUSY`. `createTray` returns a handle with `update` and async `remove`. Images distinguish SF Symbol names from local files; symbol images are macOS-specific. Dock/taskbar menu registrations live in `/system` and have separate native availability. ## [Local and global shortcuts](https://legend.so/spark/menus/#local-and-global-shortcuts) See [Keyboard shortcuts](https://legend.so/spark/keyboard-shortcuts/) for the full registration, routing, capture, and persistence guide. ```ts import { registerShortcut } from '@legendapp/spark/shortcuts'; const shortcut = await registerShortcut('CmdOrCtrl+S', () => { console.log('Save requested'); }, { windowId: 'main' }); await shortcut.remove(); ``` Local and global registration share portable accelerator parsing and async removal. Local options include window ownership and repeat behavior. `registerGlobalShortcut(accelerator, handler, { repeat? })` lives under `/global-shortcuts`. Windows supports repeat; requesting it on macOS rejects `E_UNSUPPORTED_OPTION`. Conflicts are failures, not silently replaced registrations. ## [Commands and raw keys](https://legend.so/spark/menus/#commands-and-raw-keys) `/shortcuts/commands` includes `createHotkeyRouter`, `createHotkeyStore`, optional React bindings, capture, and settings UI. Routers arbitrate enabled commands by window/application scope and priority. The store owns versioned JSON bindings, keeps portable `CmdOrCtrl` spelling, exposes conflicts, and requires flush/close for persistence. `/shortcuts/keyboard` supplies `addKeyboardListener`, key codes, and modifier helpers. Registrations have explicit ownership; callbacks observe native `consumed`/`captured` decisions and cannot consume a key by returning a value. The low-level backend/command router currently requires macOS. Native shortcut matching never waits synchronously for JavaScript. See [menu details](https://github.com/LegendApp/legend-spark/blob/main/docs/menus.md) and [command routing](https://github.com/LegendApp/legend-spark/blob/main/docs/commands.md). [View Markdown](https://legend.so/spark/menus.md)[Report a documentation correction](https://github.com/LegendApp/legend-docs/issues/new?title=Docs%3A+Menus+and+shortcuts&body=Page%3A+https%3A%2F%2Flegend.so%2Fspark%2Fmenus%0A%0AWhat+is+incorrect+or+missing%3F%0A%0AExpected+behavior+or+supporting+source%3A%0A) --- # Updating an existing prototype Move removed imports and resource contracts to the consolidated API. Source: https://legend.so/spark/migration/ Legend Spark: Experimental — not for production. These guides cover source SDK APIs; the published preview can differ. [Check release status.](https://legend.so/spark/limitations/) Updating an older prototype requires import and contract changes. Removed exports have no compatibility aliases. Update the SDK and rebuild native runtimes whose signatures changed. For local source development, refresh the packed dependencies as well. ## [Import changes](https://legend.so/spark/migration/#import-changes) | Removed path | Current path | | --- | --- | | `/message-dialog` | `/dialogs` | | `/windows/react`, `/windows/managed`, `/windows/controls` | `/windows` | | `/app/exit` | `/app` | | `/app/recent-documents` | `/app/documents` | | `/files/scanner`, `/files/watchers`, general `/settings/paths` helpers | `/files` | | `/shortcuts/commands/storage` | `/shortcuts/commands` | | `/settings/window/options` | `/settings/window` | | `/processes/commands` | `/processes` | | `/drag-drop/views` | `/drag-drop` | | `/ui/select-controls` | `/ui` | | `/expo-metro`, `/universal` | `/metro` | All paths use the `@legendapp/spark` prefix. The singleton `/global-shortcuts/hotkeys` interface was removed; use owned accelerator registrations or explicit keyboard APIs. Application-specific music drag payloads and chrome presets belong in app code. ## [Behavior changes to review](https://legend.so/spark/migration/#behavior-changes-to-review) - **Windows:** pass explicit IDs; use unified open options/results, display-relative outer-frame geometry, instance-bound listeners/guards, and grouped `macos` chrome. Animate through `setWindowBounds` in `/windows`. - **Documents/app:** use typed file/URL requests with bounded replay; quit/close approval belongs in async guards. Save before approving shutdown. - **Dialogs/files:** branch on cancellation discriminants; message buttons use IDs and checkbox state is optional. File dialogs use `defaultPath` and extension filters. Copy/move require explicit overwrite; file watches invalidate rather than report an exact change journal. - **Settings:** validate persisted values with decoders. Observable factories are async and expose readiness, `value$`, `error$`, flush and close. General paths/IO moved to files. - **Clipboard/secure storage:** use selected Expo-shaped methods; duplicate facades were removed. Clipboard string writes return a boolean, secure missing reads return null. - **Menus/system/tray:** use typed items appropriate to each surface and owned create/update/remove registrations. Unsupported item shapes reject before publication. - **Notifications/updates:** separate content/trigger and pending/delivered operations. Native update checking acknowledges initiation; typed events report later progress. - **Processes/audio/auth:** retain owned resource handles, explicit result states, byte IO, cleanup and supported cancellation. Audio hooks return loading/ready/error, not an immediate backend player. - **SQLite/WebView:** use Spark-owned database/component/ref types rather than raw OP-SQLite/WebView backend types. - **UI:** controlled `TextInput.value` is supported; `value`/`defaultValue` are exclusive. Select/SegmentedControl share controlled values. Split panes are named, Sidebar selection requires its callback, and glass/symbol components have explicit native limits. Check feature availability on each target platform. Unknown options reject. Await async cleanup and retain failed handles for retry; a global clear-all operation is not a substitute for ownership. ## [Configuration and tooling](https://legend.so/spark/migration/#configuration-and-tooling) The tooling runs on Node 24.19.0+; Bun is optional for app consumers. The framework checkout requires the pinned Bun version; see [source setup](https://legend.so/spark/getting-started/#contributing-build-spark-from-source). Use `bun run spark ...` in the framework checkout and `npx --no-install spark ...` in consumers. Runner commands are `sdk build-runner`, `build --runner`, and `dev --runner-binary`; old prebuilt/go command spellings are removed. Expo's `dev --go` still means mobile Expo Go. Author Spark-owned desktop settings in `desktop.config.json`, preserve `projectId`, and compose existing Expo projects through `/expo-config`, `/metro`, and `/native`. Review generated templates/wrappers when updating local consumers. Old Frame native identities/data are not automatically migrated. See the [rename guide](https://github.com/LegendApp/legend-spark/blob/main/docs/legend-spark-migration.md) for more migration details. [View Markdown](https://legend.so/spark/migration.md)[Report a documentation correction](https://github.com/LegendApp/legend-docs/issues/new?title=Docs%3A+Updating+an+existing+prototype&body=Page%3A+https%3A%2F%2Flegend.so%2Fspark%2Fmigration%0A%0AWhat+is+incorrect+or+missing%3F%0A%0AExpected+behavior+or+supporting+source%3A%0A) --- # Overview How React Native, Expo Desktop, and Spark runtimes fit together. Source: https://legend.so/spark/overview/ Legend Spark: Experimental — not for production. These guides cover source SDK APIs; the published preview can differ. [Check release status.](https://legend.so/spark/limitations/) Legend Spark adds native desktop capabilities and a managed development workflow to React Native and Expo Desktop. Build React Native screens, use native windows, menus, dialogs, and files, and edit JavaScript with Fast Refresh. The public package is `@legendapp/spark`; the CLI is `spark`. The single package includes its private implementation modules. Application code imports capability paths such as `@legendapp/spark/files` rather than installing internal `@legendapp/spark-*` packages. ## [How the pieces fit](https://legend.so/spark/overview/#how-the-pieces-fit) - **React Native** renders native interfaces. Application JavaScript runs in Hermes. - **Expo** supplies mobile/web tooling and selected library backends. - **Expo Desktop** creates projects and generates native desktop hosts. - **Spark** supplies desktop APIs, native compatibility checks, runtime selection, and build/packaging orchestration. Expo CLI owns Metro and the development terminal. Node 24.19.0 or newer runs the tooling. For app consumers, npm, pnpm, Yarn, and Bun are package-manager choices; Bun is optional. The source checkout requires the pinned Bun version; see [source setup](https://legend.so/spark/getting-started/#contributing-build-spark-from-source). Neither Node nor Bun is embedded in the application. ## [Three runtime choices](https://legend.so/spark/overview/#three-runtime-choices) | Runtime | Native contents | JavaScript | | --- | --- | --- | | Spark Runner | Shared SDK development profile | Metro / Fast Refresh | | Custom development build | SDK plus app-specific native dependencies/configuration | Metro / Fast Refresh | | Standalone macOS application | Production-selected native modules | Embedded bundle | Start with a compatible Runner. Adding native dependencies, helper executables, or native configuration can require a custom build. Compatibility checks include SDK, architecture, and native signatures. JavaScript-only changes do not require rebuilding. ## [Platform scope](https://legend.so/spark/overview/#platform-scope) | Target | Available workflow | Limits | | --- | --- | --- | | macOS 14+, Apple Silicon | Runner, custom development, standalone builds and packaging | Primary target; experimental | | macOS 14+, Intel x64 | Architecture selection across builds, Runner registration/downloads and packaging | Native builds and runtime behavior still need validation; published Runner is ARM64 only | | Windows 11, x64/ARM64 | Local Runner and custom development builds | Native builds and runtime behavior still need validation; no production packaging or hosted Windows Runner | | iOS / Android / web | Expo workflows and selected shared API/UI adapters | Desktop APIs do not imply mobile/web support | | Linux / Mac App Store | Outside the supported workflow | No support claim | The source targets Expo SDK 54, React Native 0.81, and Expo Desktop `1.0.0`. See [status and limitations](https://legend.so/spark/limitations/) for platform and feature restrictions. ## [Application architecture](https://legend.so/spark/overview/#application-architecture) For Electron migrations, replace DOM/CSS screens with React Native screens and browser/main-process integrations with supported capabilities. Spark has no Electron compatibility layer. An optional [WebView](https://legend.so/spark/integrations/) can host selected web content. [Helper processes](https://legend.so/spark/integrations/) run app-supplied executables. Independent background Hermes heaps use `@react-native-runtimes/core` directly; they remain inside the app process and end when it quits. ## [Explore](https://legend.so/spark/overview/#explore) - [Getting started](https://legend.so/spark/getting-started/): create, run, and build a macOS app from npm. - [Development](https://legend.so/spark/development/) and [configuration](https://legend.so/spark/configuration/): runtimes, Expo integration, and native changes. - [API guide](https://legend.so/spark/reference/): every public capability and shared contracts. - [Native UI](https://legend.so/spark/ui/), [AI execution](https://legend.so/spark/ai/), and [examples](https://legend.so/spark/examples/). - [Packaging and updates](https://legend.so/spark/distribution/) and [migration](https://legend.so/spark/migration/). [LLM documentation index](https://legend.so/spark/llms.txt) · [Complete LLM documentation](https://legend.so/spark/llms-full.txt) [View Markdown](https://legend.so/spark/overview.md)[Report a documentation correction](https://github.com/LegendApp/legend-docs/issues/new?title=Docs%3A+Overview&body=Page%3A+https%3A%2F%2Flegend.so%2Fspark%2Foverview%0A%0AWhat+is+incorrect+or+missing%3F%0A%0AExpected+behavior+or+supporting+source%3A%0A) --- # API guide Public capability imports, ownership, errors, and resource lifetimes. Source: https://legend.so/spark/reference/ Legend Spark: Experimental — not for production. These guides cover source SDK APIs; the published preview can differ. [Check release status.](https://legend.so/spark/limitations/) Import application APIs from `@legendapp/spark/`. The single SDK archive includes private implementation modules; applications should not install or deep-import them. Hooks, imperative operations, and public types share their capability entry point. ## [Capability entry points](https://legend.so/spark/reference/#capability-entry-points) | Public path | Responsibility | Guide | | --- | --- | --- | | `/app` | App context, events, activation, quit guards | [Documents and lifecycle](https://legend.so/spark/documents/) | | `/app/documents` | File/URL requests, recent history, document controllers and reload watches | [Documents](https://legend.so/spark/documents/) | | `/windows`, `/windows/macos` | Windows, displays, cursor, geometry, React roots, native macOS chrome | [Windows](https://legend.so/spark/windows/) | | `/menus`, `/context-menu`, `/tray` | Owned app menus, native popups and tray items | [Menus](https://legend.so/spark/menus/) | | `/shortcuts`, `/global-shortcuts`, `/shortcuts/commands`, `/shortcuts/keyboard` | Accelerators, command routing, persisted bindings and low-level keyboard events | [Keyboard shortcuts](https://legend.so/spark/keyboard-shortcuts/) | | `/dialogs` | Open/save panels, message dialogs and confirmation | [Files and dialogs](https://legend.so/spark/files/) | | `/files` | Paths, directories, byte/stream/positional IO, watches, scanning, Trash | [Files](https://legend.so/spark/files/) | | `/settings`, `/settings/observable`, `/secure-storage` | JSON settings, Legend State persistence and selected SecureStore methods | [Settings](https://legend.so/spark/settings/) | | `/settings/window` | Normal/virtualized settings-window composition and options | [Native UI](https://legend.so/spark/ui/) | | `/clipboard`, `/links` | Clipboard formats and external URLs/paths | [Desktop integrations](https://legend.so/spark/integrations/) | | `/drag-drop` | Native drag sources and drop destinations with generic payloads | [Drag and drop](https://legend.so/spark/drag-drop/) | | `/notifications`, `/system` | Permissions/local notifications, Dock/taskbar, login startup and power | [Desktop integrations](https://legend.so/spark/integrations/) | | `/processes` | Child processes and app-supplied helper executables | [Desktop integrations](https://legend.so/spark/integrations/) | | `/sqlite`, `/webview` | Spark-owned database and embedded browser contracts | [Desktop integrations](https://legend.so/spark/integrations/) | | `/audio`, `/auth-session` | Async players/media sessions and prepared browser-auth sessions | [Desktop integrations](https://legend.so/spark/integrations/) | | `/updates` | Native whole-app update status, initiation, preferences and events | [Packaging and updates](https://legend.so/spark/distribution/) | | `/ui`, `/ui/uniwind`, `/ui/classnames` | Common native controls and optional styling integrations | [Native UI](https://legend.so/spark/ui/) | | `/markdown`, `/html` | Read-only rich text, controlled inputs, and editable previews | [Rich text](https://legend.so/spark/rich-text/) | | `/ui/search`, `/ui/sidebar`, `/ui/split-view`, `/ui/swipe`, `/ui/glass`, `/ui/symbol` | Specialized macOS views | [Native UI](https://legend.so/spark/ui/) | | `/ai`, `/ai/codex` | Installed AI CLI execution and optional macOS Codex supervisor | [AI execution](https://legend.so/spark/ai/) | | `/contracts`, `/diagnostics` | Shared error/availability/resource types and explicit diagnostic utilities | [Shared contracts](https://legend.so/spark/reference/#shared-contracts) | Support varies by platform. Check feature-specific availability, permissions, and [platform limits](https://legend.so/spark/limitations/). ## [Shared contracts](https://legend.so/spark/reference/#shared-contracts) Spark-owned commands generally resolve `void`; data has explicit result types. Expected cancellation, veto, and nonzero process exit are results. Operational failures reject with `SparkError`; branch on `code` rather than parsing messages. `cause` retains underlying failures. Availability is advisory and separate from permissions. Unknown option keys reject with `E_UNSUPPORTED_OPTION`, including unknown keys whose value is `undefined`. Promise operations reject validation failures; synchronous utilities throw. Omission leaves update fields unchanged; `null` only clears declared nullable fields. Check each operation's merge/replace rules. Subscriptions expose synchronous `remove()`. Async registrations resolve to a handle with asynchronous `remove()`. Resource-specific cleanup uses `close()`, `remove()`, `terminate()`, or `dismiss()` according to its meaning. Stop observing when the owner ends, await required cleanup, and retain failed handles for supported retry. Concurrent cleanup joins rather than starting duplicate removals. Hooks handle late setup completions and expose cleanup-error callbacks where applicable. Time is explicit: `timeoutMs`/Unix timestamps use milliseconds; audio positions and notification delays use seconds. File/process bytes are `Uint8Array`. Native paths, URLs, and base64 are distinct data forms. ## [Ecosystem boundaries](https://legend.so/spark/reference/#ecosystem-boundaries) Spark owns the supported SQLite, WebView, audio, UI, and desktop contracts. Their backend objects stay private. Selected Clipboard, Linking, and SecureStore methods follow Expo semantics; that does not promise complete Expo-package compatibility. Legend State observables, React Native, Runtimes, Uniwind, and other ecosystem libraries retain their own APIs. Use upstream imports for capabilities beyond Spark's subset and define resource ownership when mixing integrations. ## [Tooling and metadata exports](https://legend.so/spark/reference/#tooling-and-metadata-exports) These are not application runtime capabilities: | Paths | Use | | --- | --- | | `/config`, `/schema.json`, `/config-plugin`, `/expo-config` | Typed configuration, schema and Expo composition | | `/metro`, `/native` | Metro and native discovery | | `/runtime-entry`, `/metro-gate`, `/init-template` | Generated-project runtime/bootstrap integration | | `/cli` | CLI executable entry | | `/package.json` | Package metadata | For changes to older imports and contracts, see [migration](https://legend.so/spark/migration/). Contributors should follow the repository's [API design rules](https://github.com/LegendApp/legend-spark/blob/main/docs/api-design.md) when adding APIs or replacing backends. [View Markdown](https://legend.so/spark/reference.md)[Report a documentation correction](https://github.com/LegendApp/legend-docs/issues/new?title=Docs%3A+API+guide&body=Page%3A+https%3A%2F%2Flegend.so%2Fspark%2Freference%0A%0AWhat+is+incorrect+or+missing%3F%0A%0AExpected+behavior+or+supporting+source%3A%0A) --- # Rich text Read and edit Markdown and HTML with native text views. Source: https://legend.so/spark/rich-text/ Legend Spark: Experimental — not for production. These guides cover source SDK APIs; the published preview can differ. [Check release status.](https://legend.so/spark/limitations/) Import `Markdown`, `MarkdownInput`, and `EditableMarkdown` from `@legendapp/spark/markdown`, or `Html`, `HtmlInput`, and `EditableHtml` from `@legendapp/spark/html`. Spark bundles the Enriched backends and applies their native compatibility patches during installation and native preparation; no separate backend installation is needed. macOS 14+ (Apple Silicon) supported; Windows preview, not yet verified. Rich text is unsupported on Windows. See [backend limits](https://legend.so/spark/rich-text/#backend-limits) before choosing content and styles. ## [Read-only readers](https://legend.so/spark/rich-text/#read-only-readers) ```tsx import { Markdown } from '@legendapp/spark/markdown'; import { Html } from '@legendapp/spark/html'; export function Readers() { return ( <> console.log(url)} /> {'

Hello

Read Spark.

'} ); } ``` Both readers accept these props: | Prop | Behavior | | --- | --- | | `value?: string`, `children?: string` | `value` takes precedence, including an empty string. With neither, the reader is empty. | | `selectable?: boolean` | Enables native text selection; defaults to `true`. | | `style` | React Native text style or style array. Markdown applies it to its container; HTML passes it to its text view. | | `onLinkPress({ url })` | The application owns link navigation. The example logs the URL. | | View props | Ordinary view accessibility and test props are accepted. | `Markdown` also accepts `markdownStyle` for text and block formatting. It renders headings, lists, links, code, and images; task checkboxes stay read-only. `Html` accepts `htmlStyle`, with element and style support determined by the backend. ## [Controlled inputs](https://legend.so/spark/rich-text/#controlled-inputs) `MarkdownInput` and `HtmlInput` stay in editor mode. `EditableMarkdown` and `EditableHtml` show rendered content while unfocused: click to edit, then blur or press Escape to finish. ```tsx import { useState } from 'react'; import { MarkdownInput, EditableMarkdown } from '@legendapp/spark/markdown'; import { HtmlInput, EditableHtml } from '@legendapp/spark/html'; export function Editors() { const [markdown, setMarkdown] = useState('Hello **Spark**'); const [html, setHtml] = useState('

Hello Spark

'); return ( <> ); } ``` All four require `value: string` and `onChangeText(value: string)`. They also accept `placeholder`, `autoFocus`, `onFocus`, `onBlur`, React Native `style`, `accessibilityLabel`, and `testID`. Changes arrive while typing as Markdown or HTML strings, including whitespace. A ref exposes `focus()` and `blur()` through `MarkdownInputRef` or `HtmlInputRef`, exported from the corresponding capability path. `MarkdownInput` and `EditableMarkdown` also accept `markdownStyle`. Reader props such as `selectable`, `htmlStyle`, and `onLinkPress` are not input props. ### [Formatting](https://legend.so/spark/rich-text/#formatting) Set `formatting` to show Bold, Italic, Link, Bullets, and Numbers buttons. Supply a synchronous `onRequestLink={() => url}` returning a string or `undefined` to enable Link. On macOS, formatting also enables Cmd-B, Cmd-I, Cmd-K, Cmd-Shift-7, and Cmd-Shift-8 for those actions while the input is focused. ### [Selection and controlled values](https://legend.so/spark/rich-text/#selection-and-controlled-values) The editable variants retain the native input across focus changes so native hit testing places the caret. Selection positions belong to the native plain-text view, including styled text and emoji; they are not Markdown source offsets. The public inputs do not expose a controlled selection prop. Echoing an accepted edit back through `value` leaves native selection and undo intact. An external replacement or rejected edit may reset selection. Editors serialize supported formatting, so editing does not guarantee the original Markdown or HTML spelling survives unchanged. Use an opaque `style.backgroundColor` for editable previews; the reader overlay defaults to white. Reader/editor layout parity, particularly custom block styles and line heights, still needs native acceptance. ## [Backend limits](https://legend.so/spark/rich-text/#backend-limits) - The macOS HTML backend supports basic inline formatting, paragraphs, headings, links, and lists. - Editing HTML flattens nested lists. Remote image URLs round-trip without loading their images; readers show placeholders. - The macOS backend does not apply custom `htmlStyle`, including HTML block styles. - Markdown math and syntax highlighting depend on application Enriched build settings. See the [rich-text source guide](https://github.com/LegendApp/legend-spark/blob/261e2c337905d501d86f8b38b9036feb4ed746b4/docs/rich-text.md) for backend configuration and native validation details. [View Markdown](https://legend.so/spark/rich-text.md)[Report a documentation correction](https://github.com/LegendApp/legend-docs/issues/new?title=Docs%3A+Rich+text&body=Page%3A+https%3A%2F%2Flegend.so%2Fspark%2Frich-text%0A%0AWhat+is+incorrect+or+missing%3F%0A%0AExpected+behavior+or+supporting+source%3A%0A) --- # Settings and persistence Project-scoped JSON, validated reads, Legend State files, and secure values. Source: https://legend.so/spark/settings/ Legend Spark: Experimental — not for production. These guides cover source SDK APIs; the published preview can differ. [Check release status.](https://legend.so/spark/limitations/) ## [JSON settings](https://legend.so/spark/settings/#json-settings) `@legendapp/spark/settings` exports the default project-scoped `settings` store and `createSettingsStore({ storage })`. Missing reads return `undefined`; stored JSON null stays null. Corrupt data is preserved and reported rather than silently repaired. ```ts import { settings } from '@legendapp/spark/settings'; await settings.set('theme', 'dark'); const theme = await settings.get('theme', { decode(value) { if (value !== 'system' && value !== 'light' && value !== 'dark') { throw new Error('Invalid theme preference'); } return value; }, }); ``` A decoder validates persisted data before returning an application type. Sets snapshot finite JSON values. Operations serialize per key within one store, without cross-process or cross-store locking. An update callback must not await another operation on its own key/store. Custom `SettingsStorage` adapters implement async `read`, `write`, and `remove`; keys and storage choice are explicit. General directories/path helpers live in `/files`, not `/settings/paths`. ## [Observable persistence](https://legend.so/spark/settings/#observable-persistence) `@legendapp/spark/settings/observable` is explicitly a Legend State integration. Legend State owns its observable model and React subscriptions. ```ts import { createObservableFile } from '@legendapp/spark/settings/observable'; import { getDirectory, mkdir } from '@legendapp/spark/files'; const directory = `${await getDirectory('data')}/preferences`; await mkdir(directory); const preferences = await createObservableFile({ path: `${directory}/count.json`, initialValue: 0, decode(value) { if (typeof value !== 'number') throw new Error('Expected a number'); return value; }, debounceMs: 300, }); preferences.value$.set(1); await preferences.flush(); await preferences.close(); ``` Await the factory for loaded/decoded readiness. Missing files use the initial value; corruption/read/decode errors reject. Parent directories must exist, and the path includes its extension. `saveDefault: true` persists a missing default before readiness. `flush` snapshots synchronously, including changes made in a Legend State batch, and acknowledges that snapshot behind earlier writes. Later mutations need another save. Automatic failures appear in `error$`; explicit flush/close rejects. Successful saves clear the error; retry policy belongs to the app. `close` stops observing and flushes a final snapshot. Concurrent closes join; failed closes retry that snapshot. Later observable mutations are valid but no longer persisted. Await close inside a quit guard. `createObservableSettings({ path, fields, ...options })` uses field defaults and required decoders. Defaults apply only to absent fields, present null reaches the decoder, and unknown fields are omitted. These integrations retain Legend State Date/Map/Set serialization; basic JSON settings do not acquire that larger contract. Multiple handles writing one file can overwrite each other. ## [Secure storage](https://legend.so/spark/settings/#secure-storage) `@legendapp/spark/secure-storage` exposes the selected Expo SecureStore-shaped API, including `getItemAsync`, `setItemAsync`, `deleteItemAsync`, and `isAvailableAsync`. Missing values are null. Supported option/result semantics follow that subset; unsupported options reject. Native storage remains project-scoped; it is not an OS sandbox. Web secure storage is explicitly unavailable. Check availability and platform constraints rather than treating ordinary web storage as an equivalent secure backend. See [settings details](https://github.com/LegendApp/legend-spark/blob/main/docs/settings.md) and [API contracts](https://github.com/LegendApp/legend-spark/blob/main/docs/api-contracts.md). [View Markdown](https://legend.so/spark/settings.md)[Report a documentation correction](https://github.com/LegendApp/legend-docs/issues/new?title=Docs%3A+Settings+and+persistence&body=Page%3A+https%3A%2F%2Flegend.so%2Fspark%2Fsettings%0A%0AWhat+is+incorrect+or+missing%3F%0A%0AExpected+behavior+or+supporting+source%3A%0A) --- # Native UI and styling Controlled inputs, native selectors, macOS composition, settings windows, and Uniwind. Source: https://legend.so/spark/ui/ Legend Spark: Experimental — not for production. These guides cover source SDK APIs; the published preview can differ. [Check release status.](https://legend.so/spark/limitations/) ## [Common controls](https://legend.so/spark/ui/#common-controls) For Markdown and HTML readers, inputs, and editable previews, see [Rich text](https://legend.so/spark/rich-text/). Import `Button`, `TextInput`, `Select`, and `SegmentedControl` from `@legendapp/spark/ui`: ```tsx import { useState } from 'react'; import { View } from 'react-native'; import { Button, TextInput, Select } from '@legendapp/spark/ui'; export function Preferences() { const [name, setName] = useState(''); const [theme, setTheme] = useState('system'); return (