postMessage. This page is the whole of that surface. If a message is not in these tables, Rundock does not carry it: it is refused, and the refusal is sent back so you can see what went wrong.
For a worked example of these messages in use, see Build an extension.
Sending a message
Post to the parent window with a wildcard target. Your view’s origin is opaque, so it cannot name Rundock’s origin, and Rundock checks the sending window instead.event.source is window.parent.
What a view can send
A writable view keeps no save timer of its own: send
change as the person edits, and Rundock decides when to write. It writes the file the change was made in even if the person has moved to another, and at once when the file is closed with an edit still waiting.
What Rundock sends a view
That is everything a view is told. It learns nothing else about the workspace or the page around it, and nothing about a conversation: an honoured
ask is answered with nothing, and no conversation, reply or connection ever reaches a view.
Named sources
A dashboard over several files is built by naming them. The person lists the files in the dashboard note’s frontmatter, as exact paths from the workspace root:sources: [a.csv, b.csv]) both work, and "[[Investments/Holdings.csv]]" is read as a spelling of that exact path. Nothing is searched for or matched by pattern, so the only files a view is ever handed are ones a person typed.
A view is handed its note’s sources only when all three hold:
- The manifest declares
"sources": true, which it may do only together withdeclares.sourcesis a common frontmatter key, so an extension claiming every note of a type could otherwise collect every note’s list. - The view claimed this note by that
declaresmarker. - The note lists files.
init.sources and sources.sources is one name the note lists, in its order: { path, content } for a file handed over, or { path, refused } naming the rule that refused it. path is the name as the note wrote it, never where the file really lives, and nothing else about a file is sent.
A name is refused, with the rule named and never the target, when it is absolute, starts with ~ or a drive letter, uses a backslash, has a ., .. or empty segment, contains a pattern character, an alias or a heading (|, #), has any segment starting with a dot, is a linked file or passes through a linked folder, is a file with a second name elsewhere (a hard link), is a folder, is missing, is listed twice, or is the note itself under any spelling. A note may list at most 12 names.
No write a view causes may change the sources list of the file it writes. That holds for save, change, saveSource and changeSource, and whether or not the extension declared sources, because a view that could rewrite its note’s list could name any file and be handed it next time. An edit that keeps the list is allowed. A view embedded in another note is always handed an empty list.
Asking an agent
A view can offer the person a way to ask an agent about what it shows. It never reaches the agent itself: it drafts, and the person decides. The manifest names the agents the view may ask, asasks: one to four agent ids, checked at install. After a click inside the view, ask opens a new conversation with that agent (never the open one), puts the message in the message box, and shows a line saying which extension drafted it from which file and that nothing has been sent. The person sends it, changes it or leaves it, and a conversation nobody sends is never saved. Anything the person had typed and not sent in another conversation stays there.
Before the message reaches the box, control characters other than tab and newline, bidirectional marks, overrides and isolates, and zero-width characters are removed, so the box shows exactly what would be sent.
An ask is refused, with a reason, without a click inside the view, for an agent the manifest did not name, for a named agent that is not on the team, from an embedded view, and for a shape the table does not allow. On success nothing comes back: the view never learns a conversation, the reply, or whether the person sent the draft.
A view’s own state
A view has preferences that are nobody’s data: the widths someone dragged, a tab they chose. Rundock keeps them for the view, one state per extension per note, so a view never has to write them into the person’s note. Every extension gets this, with nothing to declare, and the install screen says so. A view sends its whole state withsetState, and receives it back in init.state the next time it opens the note. A view built on Rundock UI uses Rundock.viewState, which also has the state ready before init arrives.
- Plain JSON only. Objects with string keys, arrays, strings, finite numbers, booleans and
null, at most 16 levels deep and 64 KB once serialised. ADate,Map,Set, typed array,RegExp,Blob,undefined, a hole in an array,NaN,Infinity, an object that is not plain, or a cycle is refused. Each extension’s state is also capped in total, at 1 MB and 1,000 notes. A write that would pass either is refused, and a write that shrinks or removes state is always allowed. - Every refusal is named. The view receives
refusedwithof: 'setState'and the reason, such as “the view state is larger than 64 KB”. - Written after a pause. A burst of changes, such as a column being dragged, is written once, and a pending write is made when the person opens another file.
- Embedded views read, never write. A view shown inside another note receives its state, and every
setStatefrom it is refused. - The last write wins. Two views of one note, one opened and one embedded, each read the state once, when they open, and neither is told about the other’s writes.
- Stored per machine. The state lives in
.rundock/extension-state/<extension>/, one file per note..rundock/is not meant to sync, so a workspace opened on another machine may not carry it. A state file is never handed to any view. - Renames start afresh. A note’s state is kept under its path, so renaming or moving the note starts its state again.
- Kept on update and when switched off, removed on uninstall. Uninstalling the package removes the extension’s whole state folder, and leaves every other extension’s state as it was.
One click, one request
open, openExternal and ask each act outside the view, so each needs the person’s click inside it.
- One click authorises one request, across every view. Once any view’s
open,openExternaloraskis honoured on a click, that click is used up for every view until the browser reports that it has lapsed, a few seconds later. - A view opened by a click does not inherit it. A view that appears while a click is still live, such as the note another view’s button just opened, cannot use that click as its own.
- A person’s first click works whenever no earlier click is still live.
you dismissed this in Rundock. One request waits at a time; any other is refused with Rundock is already asking you about another request. Leaving the file clears the bar.
A request with no click in the view at all is refused, and Rundock says so in the same place: “The investment-partner extension tried to open Investment Dashboard.md without you asking, so Rundock stopped it.” The view is told Rundock stopped this because it did not come from your click.
These reasons are written for the person, because a view may show them. Rundock has already told the person about a refused open, openExternal or ask, so a view should not show a notice of its own for one.
Limits
The lifecycle
- Opening a file creates a fresh view:
ready, theninit. - Changing theme restyles a view that named
'theme'inready’shandles, in place, and it keeps everything it holds, including edits not yet saved. A view that did not name it is rebuilt from scratch with the new theme’s colours and receives a newinit, and nothing held in memory survives. See Theme changes. - A note that stops claiming the view releases it. If the open file loses the marker the extension claimed it by, for example because a save took the marker out, or the claims change, the view is removed and the file is shown on the plain rendering with the reason named. A file now claimed by another extension opens in that one.
- Updating the extension’s package while its view is open swaps the view to the new code, including when only the commit changed and the version did not. An edit the view was still waiting to save is saved first.
- Switching off or uninstalling the extension while its view is open removes the view cleanly. A message that arrives afterwards from the old view is ignored.
- Embedding a file in another note mounts a separate, read-only view on that file alone:
save,change,openandaskfrom it are refused, and it is handed no sources. - Failing, by sending
error, throwing, or never sendingready, removes the view and brings the plain rendering back, with the reason shown. A failing extension cannot break the pane it was drawn in.
Theme changes
A view that can take a theme change without being rebuilt says so in itsready:
theme message carrying the new theme and the colour values read from the page at that moment. Before your own listener sees it, Rundock rewrites the block of colours in the frame and sets or clears the light class on the body, so Rundock UI and every style you wrote with var(--...) follow with nothing rebuilt. Your listener then sees the same message, for anything you drew from init’s theme yourself. The view keeps its state, including edits it has not yet sent.
A view that does not name 'theme' is rebuilt instead: a fresh frame with the new theme’s colours, a fresh ready, and an init carrying the new theme and the file as Rundock last handed it. It loses anything held only in memory, such as scroll position, and an editing view loses anything it had not yet sent. A view that edits should name 'theme'.
What Rundock puts in the view
Before your code, Rundock inlines, in this order:- Rundock’s colours as CSS custom properties on
:root, with the values of the theme showing, such as--text-1,--text-2,--border,--accent,--accent-action,--accent-control,--base,--cardand--elevated. - A floor of element styles so plain HTML, including headings, paragraphs, tables and lists, looks like Rundock.
- Rundock UI’s stylesheet.
- Your declared
styles.
window.Rundock.ui exists when your script starts.
The floor uses element selectors only and Rundock UI’s classes all start with rui-, so your own rules override both. In the light theme the body carries the class light. The frame fills the pane and your body carries a note’s padding: see Where a view sits.
Fenced blocks
An extension that setsdraws in its manifest runs in a hidden frame and exchanges a different set of messages.
Answer within ten seconds, or the request counts as the extension failing. Rundock builds the drawing afresh from the SVG elements and attributes it understands, so scripts,
foreignObject, images, use, links and event handlers never appear on the page, and a url() reference must point inside the drawing.