Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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 application
  • tauri-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-pwa
  • tauri-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-desktop replaces tauri-desktop as the approved native desktop runtime target. The plugin-host architecture described in sections 3–9 remains valid and should be re-read with electron-desktop substituted for tauri-desktop.

2. Plugin format compatibility

Community plugins keep the Obsidian-compatible on-disk format:

  • manifest.json
  • main.js
  • optional plugin assets such as styles.css and data.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:

  • supportedRuntimes
  • requiredCapabilities
  • executionHints

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:

  1. resolve vault and runtime environment
  2. construct the renderer App
  3. register bundled plugins and runtime dependencies
  4. construct the host registry for the active runtime
  5. discover and activate community plugins through the selected host
  6. 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.
  • PluginManager becomes 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

  1. Replace stale Electron-specific terminology in architecture docs and platform modeling with web-pwa and tauri-desktop runtime language.
  2. Introduce an explicit runtime-environment model in the codebase so platform detection is injected rather than hardcoded.
  3. Extract the current plugin evaluation seam in PluginManager behind a runtime-neutral host interface.
  4. Replace direct community-plugin access to the full renderer App graph with capability-backed facades.
  5. Implement the web/PWA host first as the constrained renderer host, including structured unsupported-API diagnostics.
  6. Implement the Tauri desktop sidecar host and the structured protocol between the renderer and the sidecar.
  7. Adapt boot flow so runtime resolution and host-registry setup happen before community-plugin activation while preserving plugins-before-layout ordering.
  8. Define the first declarative contribution model for commands, settings tabs, status-bar items, and other renderer-safe UI contributions.
  9. Extend the community-plugin settings and diagnostics UI with runtime mode, capability information, last failure state, and restart or disable controls.
  10. Add verification coverage for runtime-aware preflight, host lifecycle behavior, crash and timeout handling, and cross-runtime boot guarantees.
  11. Keep implementation pages truthful while this work is in flight, and update them only as code actually lands.