ADR-001: Plugin Runtime Rework
- Status: Accepted (desktop runtime direction superseded in part by ADR-003)
- Date: 2026-04-19
- Owners: Runtime and shell maintainers
Context
Lapis Notes currently executes community plugins entirely inside the active renderer/runtime process. PluginManager reads manifest.json, evaluates main.js through new Function(...), injects dependencies through a renderer-local require() map, and drives plugin lifecycle hooks directly. The recent plugin hardening work improved rollback, runtime states, and failure reporting, but it did not create a runtime-neutral execution boundary. A plugin that blocks the renderer still blocks the app, and browser-hosted execution cannot offer CPU preemption.
At the same time, the repository still carries stale desktop assumptions from an older Electron-oriented packaging experiment. The checked-in packages/desktop tree is not a clear source-level shell, but some platform and spec language still imply an Electron desktop target. That is no longer the intended direction.
The accepted product targets are now:
web-pwa— a browser-hosted and installable web applicationtauri-desktop— a native desktop distribution built around the same renderer/runtime packages
The plugin system must support both targets without abandoning the existing Obsidian-compatible plugin disk layout. Community plugins should continue to live under /.obsidian/plugins/<id>/ with manifest.json, main.js, and optional assets such as styles.css.
The documentation also has a truthfulness constraint: implementation pages under 30-implementation/ and current API/component pages should remain accurate about what the code does today. The target rework therefore needs to be captured as a decision record first, with implementation pages linking to this ADR rather than pretending the new host architecture already exists.
Decision
Lapis Notes will adopt a cross-runtime plugin host architecture with one community-plugin contract and runtime-specific host implementations.
1. Runtime targets
The only supported host targets for this rework are:
web-pwatauri-desktop
Electron is out of scope. Existing Electron-era artifacts in the repository are treated as legacy implementation residue, not as the architectural direction.
Note (2026-05-17): The choice of Tauri as the first-party native desktop shell was superseded by ADR-003.
electron-desktopreplacestauri-desktopas the approved native desktop runtime target. The plugin-host architecture described in sections 3–9 remains valid and should be re-read withelectron-desktopsubstituted fortauri-desktop.
2. Plugin format compatibility
Community plugins keep the Obsidian-compatible on-disk format:
manifest.jsonmain.js- optional plugin assets such as
styles.cssanddata.json
The rework changes where and how plugin code executes. It does not introduce a new packaging format as the default path.
3. One lifecycle authority, multiple execution hosts
PluginManager remains the lifecycle authority for community plugins. It continues to own:
- manifest discovery
- preflight validation
- runtime state transitions
- activation and deactivation ordering
- failure reporting
- boot-order guarantees
Actual plugin execution moves behind a runtime-neutral host abstraction selected by the active runtime. This host abstraction is responsible for:
- module evaluation
- dependency and capability binding
- host diagnostics and structured errors
- timeout and heartbeat integration where supported
4. Runtime-specific host implementations
Two host implementations are approved.
Web/PWA host
The web/PWA path remains renderer-hosted. It may use workers for serializable background work, but it does not promise a true process boundary. Its guarantees are:
- manifest preflight before evaluation
- transactional lifecycle and rollback
- capability-gated access to runtime services
- explicit diagnostics for unsupported APIs
- non-fatal plugin failure handling
It does not guarantee:
- CPU preemption
- hard process isolation
- transparent support for raw DOM or live editor-object tunneling
Tauri desktop host
The Tauri desktop path introduces the first stronger host boundary for community plugins. Community plugins run in a JS-capable sidecar-style host managed by the Tauri shell rather than directly inside the renderer. The desktop host owns:
- sidecar lifecycle
- IPC transport
- native capability brokering
- crash recovery and restart controls
- policy enforcement for hosted community plugins
The Tauri Rust shell is not expected to evaluate arbitrary community-plugin CommonJS bundles directly. The approved direction assumes a dedicated JS-capable host process for that responsibility.
5. Renderer-local bundled plugins in the first rollout
Bundled and first-party plugins remain local to the renderer/runtime in the first rollout of this rework. The host abstraction initially applies only to community plugins. This keeps the migration surface smaller and preserves current boot behavior for core application features.
6. Capability-gated community-plugin API
Community plugins will no longer receive the real App graph directly as the primary boundary. Instead, they receive capability-backed access to approved services. The initial capability set is intentionally smaller than the full current renderer API and starts with background-safe operations:
- vault I/O
- plugin settings storage
- command registration and unregistration
- notices and logging
- configuration reads
- metadata queries
- limited event subscription
The following are not guaranteed in the first hosted phase:
- raw DOM access across the host boundary
- direct view-constructor registration across the host boundary
- live CodeMirror objects across the host boundary
- arbitrary renderer mutation by community plugins
Richer UI and editor features are expected to move toward declarative contributions and renderer-companion patterns rather than direct object tunneling.
7. Manifest and preflight policy
The current manifest remains the documented baseline contract. The approved direction allows Lapis-specific manifest extensions, added only when implementation is ready, for example:
supportedRuntimesrequiredCapabilitiesexecutionHints
The existing isDesktopOnly field remains meaningful and maps to tauri-desktop compatibility during preflight.
Preflight becomes runtime-aware. A plugin may be rejected before evaluation if:
- the active runtime is unsupported
- required capabilities are unavailable
- the plugin requires a desktop-only boundary while running in web/PWA
- required entry files are missing
8. Boot-order invariants
The runtime rework must preserve the existing invariant that plugin activation completes before layout restoration. The approved boot order is:
- resolve vault and runtime environment
- construct the renderer
App - register bundled plugins and runtime dependencies
- construct the host registry for the active runtime
- discover and activate community plugins through the selected host
- restore layout only after activation attempts complete
This preserves the current plugins-before-layout guarantee while allowing host-specific setup before community-plugin activation.
Consequences
- Tauri desktop gains a meaningful isolation story for third-party community plugins, including stronger crash containment and recovery controls.
- Web/PWA remains first-class, but its plugin host is still constrained by the browser execution model and cannot promise hard isolation.
PluginManagerbecomes more important, not less. It remains the central owner of lifecycle, state, and boot invariants even after host extraction.- The current direct
require('obsidian')boundary becomes an internal implementation detail that must be replaced or wrapped by capability-backed facades for community plugins. - Community-plugin compatibility expands in stages rather than all at once. UI-heavy integrations will need new contribution models.
- Manifest preflight and diagnostics become more complex because they now depend on runtime and capability availability, not just version checks and file existence.
- The repository needs a real Tauri host package or equivalent source representation. Legacy Electron packaging artifacts no longer define the desktop architecture.
- Documentation must stay split by truth level: implementation pages describe the current loader, and this ADR defines the approved target state for later work.
Alternatives Considered
1. Keep renderer-only execution for all runtimes
Rejected. It preserves the simplest implementation, but it leaves desktop without a strong containment story and does not address the requirement that a community plugin should not be able to block the entire app.
2. Continue designing around Electron
Rejected at the time of this ADR. See ADR-003 for the subsequent reversal of this decision.
3. Introduce a new non-Obsidian plugin packaging format
Rejected. Preserving the existing disk layout and CommonJS plugin entry keeps vault compatibility, avoids unnecessary migration cost, and preserves the current community-plugin loading expectations.
4. Move bundled plugins and community plugins behind the remote host immediately
Rejected. Bundled plugins are part of the core application feature set and already share the renderer/runtime lifecycle. Moving them out of process in the first rollout would expand the migration surface significantly and slow delivery of the community-plugin safety improvements that motivated this ADR.
5. Expose the real renderer App graph over IPC to hosted plugins
Rejected. That would preserve too much of the current trust model, make host boundaries leaky, and undermine the point of capability-based policy and runtime-aware preflight.
Follow-up
- Replace stale Electron-specific terminology in architecture docs and platform modeling with
web-pwaandtauri-desktopruntime language. - Introduce an explicit runtime-environment model in the codebase so platform detection is injected rather than hardcoded.
- Extract the current plugin evaluation seam in
PluginManagerbehind a runtime-neutral host interface. - Replace direct community-plugin access to the full renderer
Appgraph with capability-backed facades. - Implement the web/PWA host first as the constrained renderer host, including structured unsupported-API diagnostics.
- Implement the Tauri desktop sidecar host and the structured protocol between the renderer and the sidecar.
- Adapt boot flow so runtime resolution and host-registry setup happen before community-plugin activation while preserving plugins-before-layout ordering.
- Define the first declarative contribution model for commands, settings tabs, status-bar items, and other renderer-safe UI contributions.
- Extend the community-plugin settings and diagnostics UI with runtime mode, capability information, last failure state, and restart or disable controls.
- Add verification coverage for runtime-aware preflight, host lifecycle behavior, crash and timeout handling, and cross-runtime boot guarantees.
- Keep implementation pages truthful while this work is in flight, and update them only as code actually lands.