notesy::events

What happens in notesy, as it happens. A script subscribes to the events it wants, one by one or a whole kind at once, and its handler is called for each, one at a time, in order.

rustuse notesy::{events, log, Event};

pub fn ready() {
    events::on(Event::NoteSaved, |event| log::info(`saved ${event.path}`));
    events::on(Event::AnyNote, |event| log::info(event.name));
}
Function What it does
events::on(event, handler) Calls handler(event) for every event event covers: one of Event's below.
events::off(event) Forgets every handler added for it.

Event is notesy's list of what a script can hear, so a name that's wrong doesn't compile, and an editor that knows Rune can offer the rest. (A name as text, "note.saved" or "note.*", still works too, checked as the script runs.)

Hearing about notes takes the notes.read permission: every note and vault event, and Event::SecondInstance, need it. Subscribing to one without it doesn't stop the script; the refusal goes to its log and the handler never runs.

#Events

Every event reaches the handler as an object with its name (the text form) and the fields below. Paths are relative to the vault, with / between folders.

Event:: name Fields When Needs
AppReady app.ready notesy is up, with its plugins loaded
SecondInstance app.second_instance args (list of strings), cwd notesy was started again while running; that start's arguments and folder notes.read
VaultOpened vault.opened name_of_vault a vault was opened notes.read
NoteOpened note.opened path a note was opened in a tab notes.read
NoteClosed note.closed path a note's tab was closed notes.read
NoteChanged note.changed path its text changed; a few times a second at most while typing notes.read
NoteSaved note.saved path it was saved notes.read
NoteCreated note.created path a new note notes.read
NoteRenamed note.renamed from, to it was renamed or moved notes.read
NoteDeleted note.deleted path it was deleted notes.read
ThemeChanged theme.changed id, dark (bool) the theme changed
CommandRan command.ran id a command ran: its id, like plugin.you.saves.sync
SidebarItem sidebar.item section, item an item in one of its sidebar sections was clicked: section is the key it gave sections::add, item the item's id. Only the plugin whose section it is hears it.

And each kind at once:

Event:: Covers
AnyApp app.ready and app.second_instance (app.*)
AnyVault vault.opened (vault.*)
AnyNote every note. event (note.*)

Each event object also has its kind, an Event, to match on:

rustuse notesy::{events, log, Event};

pub fn ready() {
    events::on(Event::AnyNote, |event| {
        match event.kind {
            Event::NoteSaved => log::info(`saved ${event.path}`),
            Event::NoteDeleted => log::info(`gone: ${event.path}`),
            _ => {}
        }
    });
}

#Its own events

A big plugin's parts can talk without calling each other: a channel of its own carries events from one part to every part listening. Needs nothing.

rustuse notesy::{events, log};

pub fn ready() {
    let sync = events::channel("sync");
    sync.on(|done| log::info(`${done.notes} notes synced`));
    sync.emit(#{ notes: 12 });
}
Function What it does
events::channel(name) One of its own channels, by a name of its own (lowercase letters, digits, -, _, .).
channel.on(handler) handler(value) for everything emitted on it.
channel.emit(value) Sends value, plain data (numbers, text, lists and objects of those), to its handlers: after what's running now, in the order it was sent.
channel.off() Forgets every handler on it.

A channel is its plugin's alone: no other plugin hears it. At most 1,024 events wait on its channels at once; past that, emit is an error.

#Other plugins

Plugins talk to each other only when both say so. One shares, under names of its own, with the plugins.share permission; one that wants to hear it names it in its plugin.toml ([plugins] hears = ["ana.sync"], see plugin.toml), which asks the user for that too. When they approve it they see what each plugin it hears can do, and a plugin that hears one that's since been allowed more stops until they look again.

rustuse notesy::events;

// In Sync, which has plugins.share.
pub fn ready() {
    let synced = events::share("synced");
    synced.emit(#{ notes: 12, at: "2026-09-12" });
}
rustuse notesy::{events, log};

// In Board, whose plugin.toml has [plugins] hears = ["ana.sync"].
pub fn ready() {
    events::from("ana.sync", "synced").on(|done| log::info(`Sync did ${done.notes} notes`));
}
Function What it does
events::share(name) What it shares under name (lowercase letters, digits, -, _, .). Needs plugins.share.
share.emit(value) Sends value, plain data, to every running plugin that hears it, after what's running there now. At most 20 a second, of 64 KB each; past that, emit is an error.
events::from(plugin, name) What plugin shares under name: one its [plugins] hears names.
heard.on(handler) handler(value) for everything it shares under that name.
heard.off() Forgets every handler on it.

A plugin that isn't running hears nothing, and nothing's kept for one that starts later.

#Ordering and dropping

Events wait in a queue for each plugin: up to 512. A plugin that falls that far behind loses the oldest (its log says how many), never a lifecycle call (Scripts).

Every page