← All posts

Plainva 0.6.6: the alarm that meant nothing

August 15, 2026

A phone in a pocket runs no timers. That sentence is obvious once you read it, and it was the cause of the most annoying thing in 0.6.5: a red warning triangle that appeared for a sync with nothing wrong with it.

Twenty minutes of nothing, twice

Plainva’s sync worker has a watchdog. It notices when a cycle stops responding — a request that never returns, a network that vanished mid-transfer — and abandons it rather than letting the whole sync sit there forever. It measured that on the wall clock: took the time the cycle last did something, compared it to now, and past fifteen minutes declared it dead.

On a desktop that is correct. On a phone it is a category error. Lock the screen and the operating system suspends the app: no timers run, no code executes, nothing moves. Twenty minutes later you unlock it, the first watchdog tick fires, and it does the arithmetic — last activity twenty minutes ago. Which on the wall clock is indistinguishable from a request nobody answered for twenty minutes. So it abandoned a perfectly healthy cycle and put a red triangle on the screen.

The fix is small: the gap between two watchdog ticks tells it how long it was not running, and that time does not count against the cycle. But finding it meant accepting that the clock is the wrong instrument. What the watchdog actually wants to know is not “how much time passed” but “how much time passed during which I could have noticed something”.

Three sentences that lied about themselves

While I was in there I read the three places that raise a sync error, and two of them said one thing and did another.

“Appears stuck, will be abandoned if it stays unresponsive” — that is a warning about a possible problem, raised as a final error. “Was unresponsive and has been abandoned; retrying” — that is one failed attempt out of several, raised as a final error. “N files could not be pulled; they will be retried next cycle” — that one keeps the cursor exactly so it can retry, and raised a final error.

Plainva already has a rule for this: a transient failure is reported as retrying, and only the third one in a row turns red. All three now follow it.

None of this was hard. What is worth saying out loud is the cost of getting it wrong: a triangle that appears when nothing is wrong is a triangle nobody reads. By the time it means something, it has been trained into background noise. That is not a cosmetic problem — it is the failure of the one channel the app has for saying “something needs you”.

The notice that greeted you every morning

The same release had a second one of these. If an older Plainva on another device was still publishing retired account data, a notice said so. It was correct. It also said so on every single app start, because the debounce behind it lived in a module-level map — which holds for one session, and a phone starts many sessions a day.

And the condition never cleared itself. An entry nobody removes is found again next cycle, and the next, forever.

So it needed two halves, because either one alone is still wrong. A finding that needs a person to act now remembers that it was said, across restarts — a process dying is not the person acting. And the phone got the action that ends the condition, which until now existed only on the desktop; that is why the warning was permanent by construction on a phone. It could be read there and never acted on.

Transient notices deliberately kept the old session-scoped behaviour. A network error is worth stating again after a restart. The two are different things, and now they are different functions.

Where a warning would have helped

There is one more in this release, and it is the one that actually cost something.

Rename a note. Edit it later. Instead of merging, sync kept your local version as a .CONFLICT copy and replaced the note with the server’s.

The cause is one line of reasoning I never wrote down. sync_state — the table holding the common ancestor a three-way merge needs — is keyed by path, exactly like the file index. A rename rewrote the index and the sync queue, and left sync_state behind under the old name. The renamed note therefore looked brand new: recorded with local content and no base at all. Both fast-forward and three-way merge require that base, so the code fell through to its “no reliable base” branch, which does the safe thing: preserve the local version as a copy, take the remote.

That branch is right. It was reached for the wrong reason.

Nothing was lost — the .CONFLICT file next to the note holds your version verbatim, and Plainva merges the two side by side. But the sync said nothing about why, because from its own point of view nothing unusual had happened. It saw a new file with no history. There was no warning to raise.

The same shape had a sibling nobody reported: the index deleted rows by sha256(path), which is precisely the value a row loses when its folder is renamed. So a deleted note could stay in the tree and in search while it was gone from disk — and in the other direction, de-indexing an old path could remove a note that still existed. Everything keyed by a path now moves when the path moves.

The rest

The phone’s settings follow one grammar. A field used to stand loose on the page and draw its own left edge, right beside the cards on the surface next to it. There are two edges now — the page’s and the row’s — and one rhythm per page instead of a stack of individual margins. Nineteen surfaces.

Two functional things surfaced on the way, both of which had been sitting there in plain sight. The pencil on a mail account changed a heading four hundred lines below the fold, so tapping it looked like nothing had happened; it opens a sheet now. And the meetings folder was a bare text field you had to type a vault path into, while the four folders one screen over had a browser — because there was no shared folder row to reach for. There is one now.

The mobile calendar says which day it is. The time grid answered “at what time” and left “on what date” to the label above it. For one column that reads fine; for three side by side it means counting. And an all-day event had nowhere to go in that view at all — the grid positions timed blocks, so a whole-day event was simply invisible there.

A deleted calendar account stays deleted. The account list travels as one field in the shared profile, so a device on an older version keeps publishing an account it still has, and the next cycle puts it back. The tombstone is deliberately local: removing an account for every device is a larger promise, and it needs the other devices to agree.

“Recently opened” and Bookmarks stay put in the desktop sidebar. They lived inside the Files branch and vanished when you switched to Tags or Databases — and the switch itself sat above them, pushed down the sidebar, away from the tree it switches. Both moved above it.

macOS now requires 13.3 (Ventura), corrected from 12. That is not a tightening but an admission: on macOS an app draws its window with a system component that moves with OS updates and not with Safari, so a Mac with a fully current Safari can still be below the bar. Until this release the installer promised a system on which Plainva does not start.

Download

Plainva 0.6.6 is on the releases page for Windows, macOS and Linux; the desktop app updates itself. Android testers get it through the internal track, iOS through TestFlight.