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-002: Notebook Plugin and Markdown Cell Model

  • Status: Accepted
  • Date: 2026-05-03
  • Owners: Workspace, editor, runtime, and data maintainers

Context

Lapis Notes already has a strong markdown editor and preview stack, a browser-first runtime, a per-vault generated-state database, and a file-first storage model. It does not yet have notebook semantics: multiple executable cells in one note, dependency-driven execution, or persistent notebook outputs.

The target notebook feature must satisfy a specific set of constraints:

  • notes stay markdown-friendly and git-friendly
  • cells can execute in the browser and PWA host without requiring a server by default
  • the runtime computes a DAG and reruns or marks descendants stale when inputs change
  • vault data such as CSV and Parquet files can be loaded into notebook sessions
  • app-derived data such as note metadata, links, tags, and search documents can be queried from notebook cells
  • notebook outputs remain generated state instead of polluting canonical note bytes

The current AppDatabase contract is also an important constraint. It is a generated-state API for metadata and search persistence; it is not today a general-purpose notebook query engine.

Decision

Lapis Notes will pursue notebook support as a bundled first-party plugin named plugin-notebook, using notebook-flavored markdown files as the canonical document format and a browser-safe notebook runtime for execution.

1. Bundled plugin identity

The proposed plugin identity is:

  • package name: @lapis-notes/notebook
  • manifest id: plugin-notebook

Notebook support is therefore part of the first-party packaged plugin landscape rather than a separate external application.

2. Canonical notebook file format

The primary notebook format remains markdown notes.

  • canonical extension: .notebook.md
  • notebook identity: file-path suffix rather than frontmatter opt-in
  • optional notebook config: top-level notebook: frontmatter for runtime settings
  • ordinary prose remains ordinary markdown

The system does not adopt .ipynb as its primary on-disk format.

3. Executable cell syntax

The canonical executable cell unit is a markdown container directive named cell.

Cells carry stable ids and attributes through directive syntax, and code cells wrap ordinary fenced code blocks. This keeps executable cells explicit while preserving ordinary code fences for documentation snippets.

4. Runtime model

Notebook execution runs behind a notebook session runtime in browser and PWA targets.

The runtime is responsible for:

  • parsing notebook cells into a notebook document model
  • computing refs and defs
  • building a DAG
  • tracking stale, running, errored, and disabled states
  • scheduling autorun or lazy execution

The runtime should preserve notebook consistency rules similar to marimo:

  • descendants of changed cells rerun automatically or are marked stale
  • deleting a cell removes its definitions from notebook state
  • multiply defined names and dependency cycles are notebook errors
  • mutations are not tracked across cells

5. Initial cell languages

The first planned cell languages are:

  • JavaScript and TypeScript
  • SQL
  • dynamic markdown

JavaScript and TypeScript cells use static top-level analysis for refs and defs. SQL and dynamic markdown cells use explicit placeholder references such as {{name}} for cross-cell dependency edges.

6. Query and data engine

DuckDB-Wasm is the proposed relational and tabular execution engine for notebook sessions in browser and PWA runtimes.

The notebook runtime will use DuckDB-Wasm to:

  • ingest vault-backed CSV, Parquet, JSON, and other supported file formats
  • expose app-derived tables inside notebook sessions
  • execute SQL cells and ad hoc notebook data queries

7. App-derived data exposure

The first rollout exposes app-derived state to notebook sessions as read-only notebook tables or notebook mounts backed by MetadataCache and AppDatabase snapshots.

The first rollout does not treat the existing AppDatabase API as the notebook runtime’s primary arbitrary SQL execution surface. Direct SQLite bridging remains follow-on work where runtime capabilities make it safe and honest.

8. Output persistence

Notebook outputs, stale state, run metadata, and cached session artifacts are generated state.

They must be persisted outside canonical markdown note bytes, using the same general generated-state discipline already applied to metadata and search state.

9. Rich output protocol

Notebook execution will use a typed output protocol and notebook-owned renderer registry rather than relying on arbitrary imperative DOM output from cells.

The first planned rich output kinds are:

  • text
  • markdown
  • HTML
  • table
  • chart-spec outputs
  • image
  • error

The first rollout should treat interactive tables as a priority feature and support charts through approved spec-driven renderers instead of arbitrary widget code.

Consequences

  • Notebook notes stay readable, editable, and diffable as markdown files.
  • Notebook ownership is decided by file identity, which keeps view routing and command targeting self-contained instead of patching generic markdown leaves.
  • The chosen format fits the current markdown parser direction better than a separate JSON notebook model.
  • The browser and PWA runtime stay first-class, with no default requirement for a backend kernel.
  • The implementation now includes a notebook document model, a hybrid execution runtime that uses the dedicated notebook worker when practical and falls back to the in-process path for DuckDB-backed notebooks, generated-state persistence, and notebook-specific automated coverage.
  • The implementation now also includes a typed output protocol and renderer registry for rich notebook outputs.
  • SQL cell dependencies are intentionally constrained in the first rollout to explicit placeholder references rather than unrestricted SQL introspection.
  • The notebook feature becomes a substantial first-party plugin rather than a thin markdown post-processor.

Alternatives Considered

1. Adopt .ipynb as the primary notebook format

Rejected. It conflicts with the repo’s markdown-first note model and would introduce a JSON canonical format where the rest of the product is explicitly file- and markdown-first.

2. Adopt a marimo-style pure Python notebook format as the primary on-disk model

Rejected. marimo is the right semantic reference for reactivity, but a pure .py canonical format does not fit the current note model or the requirement for markdown-native notes.

3. Embed an existing notebook product such as Starboard or JupyterLite as the notebook surface

Rejected. Those projects solve different layers of the problem. They do not line up cleanly with the existing markdown editor, file model, plugin runtime, and generated-state storage already present in Lapis Notes.

4. Treat any fenced code block as an executable notebook cell

Rejected. That would blur the line between documentation snippets and executable cells, make metadata attachment awkward, and fit the current parser model worse than explicit container directives.

5. Use the current app database as the notebook runtime’s primary SQL interface

Rejected for the first rollout. The existing AppDatabase contract is intentionally narrow and should stay truthful. Notebook execution needs its own runtime and query layer, with read-only bridges back into app-generated state.

Follow-up Status

  1. The first spec pages, bundled plugin-notebook package, notebook-aware parsing/rendering, typed output protocol, generated-state persistence model, notebook commands, and Playwright coverage are implemented.
  2. The runtime now ships with DAG construction, stale propagation, scoped execution commands, DuckDB-Wasm query execution, and vault file registration.
  3. Inline per-cell controls and explorer-style notebook panels are now implemented. Richer live-preview chrome and slash-menu notebook authoring remain follow-on work.