Scripts

A plugin with main = "main.rn" in its manifest runs that script, written in Rune. notesy compiles it when the plugin starts and runs it on a thread of its own, so the window never waits for it.

#More than one file

A script can be split up with Rune's modules: mod helpers; in main.rn takes in helpers.rn beside it (or helpers/mod.rn), and its public functions are then helpers::name(). Modules can have modules of their own the same way, a folder down each time.

rust// main.rn
use notesy::log;
mod helpers;

pub fn ready() {
    log::info(`twice 2 is ${helpers::double(2)}`);
}

// helpers.rn
pub fn double(n) { n * 2 }

Every file comes from the plugin's own folder, and none through a link: a script can't take in a file from anywhere else. Packing takes every file in the folder, so the modules go with it.

#What a script can reach

Rune itself has no files, network or processes. notesy adds its own modules, under notesy::, and only the ones the plugin's permissions cover: a script that uses a module it wasn't given doesn't compile.

rustuse notesy::{events, log, settings, store};
Module Needs Page
notesy::log nothing notesy::log
notesy::events per event notesy::events
notesy::store nothing notesy::store
notesy::settings nothing notesy::settings
notesy::secrets nothing notesy::secrets
notesy::json nothing notesy::json
notesy::toml nothing notesy::toml
notesy::yaml nothing notesy::yaml
notesy::files nothing (its own folder only) notesy::files
notesy::icons, notesy::canvas nothing (its [[icons]] without a file) notesy::icons
notesy::Event nothing: what events::on takes notesy::events
notesy::{Icon, Color, Tone, Side, Method, Command} nothing: notesy's names, as enums Names: Icon, Color, Tone, Side, Method, Command, Menu, Sidebar, Event
notesy::view nothing notesy::view
notesy::commands commands notesy::commands
notesy::panels, notesy::views ui notesy::panels and notesy::views
notesy::sections ui notesy::sections
notesy::status ui notesy::status
notesy::notices notify notesy::notices
notesy::clipboard clipboard.write to copy, clipboard.read to paste notesy::clipboard
notesy::links links.open notesy::links
notesy::notes notes.read to read, notes.write to change notesy::notes
notesy::editor editor notesy::editor
notesy::net net (hosts in [net]) notesy::net
notesy::accounts accounts (and [[accounts]]) notesy::accounts
notesy::blocks markdown (languages in [contributes] blocks) notesy::blocks

How the runtime was built, and why: plan.md.

#Its lifecycle

A script may define any of these; notesy calls each one it defines, in this order over a plugin's life:

Function When
pub fn load() It's loaded, before anything else reaches it: set yourself up here.
pub fn updated(from, to) It runs another version than last time (from and to are version strings): bring your store along.
pub fn ready() notesy is up, with a vault open and its tabs back: start what needs the app or the notes. A plugin turned on later hears this right after load.
pub fn setting_changed(key) The user changed one of its settings.
pub fn unloading() It's being turned off, or notesy is quitting: save and let go, quickly.

Events it subscribed to with events::on arrive in between, one at a time, in the order they happened.

#Limits

Every call into a script (a lifecycle function, an event handler) runs within limits:

Limit Default When it's reached
Instructions per call 1,000,000 the call is halted: "it ran too long"
Instructions per view 5,000,000 the view is halted
Memory, across its calls 64 MiB the allocation fails, and the call with it (what earlier calls kept counts too)
Failures a minute 3 the plugin is stopped

Values going between a script and notesy (what it keeps, shares, sends as JSON, or builds a view from) nest at most 64 deep: a list or object nested deeper is an error where it's handed over.

A failure is a call that errors (a panic, a ? on an error, a wrong type) or is halted. Each goes to the plugin's log on the Plugins page. The third in a minute stops the plugin, with a notice saying which and why; it stays stopped until notesy starts again or it's turned off and on.

Waiting costs nothing: a script that has nothing to do isn't running.

#Errors

If the script doesn't compile, the plugin doesn't start, and its log shows what the compiler said, with the file and line:

error: Expected expression but got `;`
  ┌─ main.rn:1:26
  │
1 │ pub fn ready() { let x = ; }
  │                          ^

Errors while it runs show the function or event they happened in, like note.saved: there's no event “note.eaten”.

Warnings go to its log as it starts, and notesy plugin check prints them: the compiler's (a template with nothing in it), and what notesy knows Rune runs wrong (the trap below), each with its file and line.

#Values

What a script keeps and is given are plain values: true and false, integers, floats, strings, vectors and objects (#{ key: value }) of those. An event is an object with its name and its fields (notesy::events). Paths are always relative to the vault (Daily/2026-09-12.md), never the machine's.

#A trap in Rune 0.14

In a match on text or a number, an arm whose block ends in a variable of its own gives nothing, not the variable:

rustuse notesy::view;

fn part(kind) {
    match kind {
        // Comes out as nothing: a view with it says “item 2: nothing can't be kept”.
        "tags" => { let chips = view::chips(["#a"]); chips.target = "tag"; chips },
        _ => view::note(kind),
    }
}

Build such a part in a function of its own ("tags" => tag_chips()), or end the block in a call (view::chips(…)). A match on an Option (Some(x) => { … }) is fine.

Every page