← All posts

Plainva 0.6.0: mail that reads as conversations — and the id that was never unique

July 30, 2026

Someone asked for threaded conversations in the mail client. I thought it was a display change. It was not.

An id that identifies nothing

IMAP gives every message in a folder a number — a uid. It is unique. Inside that folder. On that account.

For as long as Plainva’s mail list showed exactly one folder of one account, that was fine, and I built on it without noticing I had made an assumption at all. A row carried its uid, an action read it back, and everything lined up.

Conversations broke the first half of that. A conversation is not a folder-shaped thing: your reply lives in Sent, the message you replied to lives in the inbox. Read them together and you have two rows whose uids mean different things.

“All inboxes” broke the second half. Two accounts, both with a message numbered 1234.

Here is the part that made me stop and rewrite rather than patch: this does not fail loudly. There is no error, no empty list, no crash. You archive a message and a different message is archived. You delete one and lose another. The failure looks like the software working.

So a row no longer carries a bare id. It carries where it came from — account and folder — and every action reads that back before it does anything. Conversations group by their reference chain first, using the headers that actually say what replied to what; subject matching is only a fallback, and only for a recognisable reply within 30 days, because “Re: lunch” in March and “Re: lunch” in November are not one conversation.

Mail asked the server first, every time

While I was in there, I found something that had been slowing mail down since the day it shipped.

Both shells read the local cache only in the catch. Network first, cache as the consolation prize. So every folder you opened — including one you had opened a minute ago — waited for a round trip before showing you anything.

That is backwards. Now the cached list appears immediately and the refresh runs behind it. The list says “updating” while that happens, which matters to me: showing you old data while implying it is current is the same class of dishonesty as a report that claims success it did not have.

The other half was worse. Every mail action opened its own connection: open a folder, read three messages, and you had signed in four times. Now a session is reused per account. On the phone it is released when the app goes to the background, because a resumed connection is dead without saying so — reusing it would hang your next action instead of failing fast.

Three holes that looked like one broken account

Then a vault turned up with a Google account and a Microsoft account. Which had never happened before — my own setup had one account per provider, and so did every test.

Three separate bugs, all wearing the same costume: “the account is broken, and signing in again does not help.”

The first: an account connected through the union consent keeps its per-service credential slot deliberately empty, because its one token lives in the account slot. The readiness check demanded that slot. So exactly those accounts were declared “not ready” — no sync target was built at all, the file sync was silently off, and mail quietly fell back to its offline copy. A Google Drive vault came up local.

The second: the broker lookup asked “a calendar token for this vault” — not for which account. With one account that works by accident. With two, the Microsoft calendar could be handed the Google token, and Google answers that with a 401 that reads exactly like a revoked sign-in.

The third is my favourite, in the way a bug can be a favourite. Adding an Outlook account broke the Google calendar. Deleting the Outlook account fixed it. Which looks like the two accounts cannot coexist — and is actually a marker that outlived the connect it belonged to and then started answering for everything in the vault.

All three are gone, and each one is now pinned by a test that fails if the account boundary blurs again.

The templates were not demonstrating anything

Different corner of the app, same kind of finding.

Plainva ships vault templates — PARA, GTD, Zettelkasten, and so on. PARA gave you one project, one task, one area. The Zettelkasten had a single permanent note, which is technically a folder. The journal shipped a database with a table view and a calendar view and no entries at all, so both views opened onto nothing.

A method template that shows you one of each thing is not teaching the method. It is showing you the empty shape of it.

So the existing templates were filled in — PARA went from 6 notes to 19, GTD from 7 to 16, the Zettelkasten from 4 to 11, with notes that link to each other because that is the entire point of a Zettelkasten. And there is a new one: the Plainva tour, a vault with nine folders, seven databases and around forty connected notes, where every view appears once with real data in it. Pinboard, calendar, gallery, board, timeline, table, a tree with sub-items. It is now the recommended starting point in the vault chooser, where the empty vault used to sit — which, on reflection, was a strange thing to recommend to someone who has never seen the app.

All of it exists in all ten languages, built from one shared structure with a string bundle per language. Mirroring forty notes by hand ten times is the most reliable way I know to let them drift apart.

A template that asks

Templates themselves got the bigger change. They used to substitute a handful of placeholders with three raw string replacements. Now there is one engine, and it can do rather more: date and weekday arithmetic, a link to the daily note N days out, questions with a default, questions with a list to pick from, a date picker, the text you had selected, the clipboard. Every question a template asks appears in one dialog — cancel it and nothing is created.

And a template can start on its own. Map a folder to a template, or a note type to one, and new notes in that folder start from it. The longest path wins, a folder beats a type, and explicitly picking one beats both.

The reason I went digging here was not the feature list. It was this: the old substitution left {{cursor}} and {{prompt:…}} sitting in the file as literal text — and every daily note built from a template inherited that template’s own frontmatter. Since every template made with the button is stamped plainva.tasks: false, those daily notes hid themselves from the task overview. Silently. There is a repair under Maintenance that shows you every affected note before it changes anything.

Smaller things you will notice

Still true

Windows builds are unsigned, so SmartScreen will warn you. Encrypted workspaces are still marked experimental and stay that way until an independent review has happened. And the mobile app is on the same version number as the desktop again — 0.6.0 on both, after the two lines drifted apart for a few releases.

Download 0.6.0 · what changed in full