← uka.dev

Simple Streams

Simple Streams is an Obsidian plugin that renders a filtered, sorted list of notes wherever you drop a stream block into a note. It is the newest thing here — first released in September 2026 — and it is not in the community plugin list yet, so it installs from a GitHub release (opens in a new tab).

A note called Travel Board in Obsidian's dark theme. Below its text, a stream renders trips grouped under a 2026 heading — Hanoi, Berlin, Zanzibar, Oaxaca — each showing its title, its date, its tags and a paragraph of the note.
One note, one block, and the trips it selected — drawn in Obsidian's own dark rendition.

The query decides what the stream is

This started as a piece of Simple Journal that wanted to be general. A journal is a filtered, sorted, grouped list of notes — newest first, one per entry, grouped by day. But so is a reading log, a list of trips, a film diary. The shape is identical; only the filter differs.

So the plugin has no idea what a journal is. You give a block a folder, some tags, a date field to sort and group on, and a display mode, and what comes back is whatever that description matches. Journal-shaped, but not journal-only: the query is the whole configuration, and there are no settings anywhere else — the plugin stores none.

A timer that outlived its plugin

The strongest thing in the second release is a bug that took a while to even be describable. Streams re-run on a short delay after the vault changes, and that delay was scheduled with a bare timer call. Which window that resolves against depends on where the code is running — and Obsidian opens notes in popout windows, each with its own set of timers.

A handle taken from one window cannot be cleared through another. So a timer armed inside a popout survived the shutdown meant to cancel it, and fired after the plugin had already unloaded. It now schedules and clears against the window it belongs to, so the handle goes back where it came from.

The second fix is smaller and more ordinary: clicking a row opened the note through a call that returns a promise, and the handler dropped it. The note a row points at can be renamed or deleted in the time between the block drawing and you clicking it, so that failure is genuinely reachable — and unhandled, it produced a console message and a link that did nothing. It now says which path it could not open.

Both were found by the community directory's review rather than by me, and the release notes say so. A plugin that reads your whole vault should not be graded by its own author alone.

What it reads, stated at install time

A query that can select from anywhere in the vault has to be able to see the vault. That is not a detail to leave in a permissions list for someone to discover later, so the README says it outright, at the top, before the feature list:

A narrowing folder in a block changes what a stream shows. The list it filters is still the whole vault, and saying that plainly is worth more than the sentence it costs.

YAML is a co-author you did not ask for

Writing the query in a code block means the query is YAML, and YAML has opinions. A tag written with its hash needs quoting, because a bare hash starts a comment and your tag silently vanishes. A comparison needs quoting for the same class of reason — an unquoted greater-than is YAML's own syntax and your condition becomes something else entirely.

Neither can be fixed by the plugin without inventing a dialect. What it does instead is refuse to fail quietly: an invalid block renders the reason where the stream would have been. A comparison YAML mangled, a sort field that resolves on nothing, a date field no note in range carries — each says so in place, rather than rendering an empty list and leaving you to wonder whether the filter is wrong or the notes are missing. There is a commit whose whole subject is naming that hash trap instead of letting it confuse people.

The habit of saying it rather than implying it

Reading the log back, one phrasing keeps recurring: count the regression instead of timing it, take the built-in list from Node instead of a package, make the minimum version checkable instead of asserted, record the locale decision instead of leaving it implicit, name the trap instead of letting it confuse. That is the same instinct as the known-limitations sections, applied to the repository rather than to the release notes.

One of those is my favourite kind of change: a dependency came out because it was a re-export of a list Node itself has exposed since version 9.3, and the built output was byte-identical without it. A dependency that changes nothing is still a dependency.

Where it is now

Two releases in two days, which is what happens when a review finds two real defects in the first one. It is read-only by design and will stay that way — it shows notes, it does not make them. It is not in the community plugin list yet; until it is, it installs from a release on GitHub (opens in a new tab), where the build carries a signed provenance attestation.