← All posts

Plainva 0.6.5: built, finished, never called

August 14, 2026

Three people reported three unrelated things: an attachment that froze the app, a folder that would not delete, and a white window on a Mac. Fixing them turned up the same shape three times — something finished, sitting in the code, that nobody ever called.

94 million numbers

The first report was the clearest. A 90 MB attachment froze Plainva, crashed it, or left a blank window. Three symptoms, and I spent a while assuming they were three problems.

They were one. Plainva’s HTTP layer turns a request body into Array.from(new Uint8Array(buffer)) and then into JSON. For 90 MB that is roughly 94 million boxed numbers — well past a gigabyte of peak memory, with the main thread blocked for minutes. Whether that ends in a freeze, a crash or a blank window depends on how much memory your machine happened to have.

The fix is not a size limit. I was asked directly whether to cap uploads, and the answer was no: the ceiling should be whatever your service allows, not whatever my architecture survives. So the content stops entering the display layer at all. A native command takes the file and streams it straight to the server. Memory stays flat whether the file is 2 MB or 2 GB.

Underneath it sat a second problem that had nothing to do with memory. The request timeout covered the upload itself — so a flat 30 seconds did not mean “the server stopped answering”, it meant “this upload has to sustain 3 MB/s”. No ordinary home connection does that for 90 MB. The abort then surfaced as a network error, and uploads deliberately never retry those, because a repeated write can duplicate a change. The file simply never arrived, and nothing said why. The budget grows with the file now, against a deliberately pessimistic floor.

One case still takes the old path, and I would rather say so than let you find it: an encrypted workspace. Sealing a file needs its plaintext bytes in hand, and streaming past that would need a chunked AEAD format — so that is a separate piece of work rather than something quietly included here.

Finished, and never called

The second thread is the one I find harder to write about, because it is not a bug in the usual sense.

A task created in Plainva stayed a note. It never appeared in Google Tasks, in the iCloud reminders, in Microsoft To Do. I had assumed this was missing provider work — that somebody would have to write the three integrations.

They were written. createTask is implemented in all three providers, complete, tested. It had, in the entire repository, no caller at all.

What was missing was never the capability. It was the answer to “which list?” — and that answer belongs to the database, not to each dialog that happens to create a task. So a task database can now name one, with None — stays a note as the first option. And all three ways a task comes into being — the + New task prompt, a promoted checkbox, a mail captured as a task — go through one service. Wiring only the first would have made it depend on where a task was born whether it reaches your provider, and it would have looked correct at all three, because the note is there either way.

The same shape turned up three more times while I was in there. A shared core for note embeds, written from the desktop code and then called by nobody. A cursor column in the calendar cache, unused since the day it was added — and every writer would have wiped it, because an omitted field read as “clear”. An abort flag for a hanging sign-in that only one function could reach, which is why closing the browser tab left the app waiting three minutes with nothing to press.

An extension is a claim, not evidence

In 0.6.4 I made .csv, .svg and .txt open in your system’s default program. That was the right call for the problem in front of me and the wrong one overall, and this release takes it back: text files open in Plainva again, with highlighting resolved from the file name.

Doing that properly needs two things I did not have.

The first is a veto. A file extension is a claim someone made, not evidence of what is inside. A rotated .log, a database dump called .csv — those decode into a lossy string, and writing that string back destroys the file. So the first bytes decide: anything that does not decode cleanly is handed to the system app instead of being shown and then quietly ruined on save.

The second is leaving the file alone. Plainva’s editor has always normalised Windows line endings to Unix ones and written them back that way. That is right for a note — Markdown in a Plainva vault is UTF-8 with LF by house rule — and wrong for an .ini that came from Windows or a .csv with the byte-order mark Excel wants. Saving one would have rewritten every line in the file: one edit, a whole-file diff, and in the case of a .bat, a change in what the file does.

The folder that would not delete

The third report read like a permissions problem. Delete a folder, get an error, folder still there.

It was an index defect, and the folder was in fact gone from disk. When a folder is renamed, the sync queue rewrites the stored path of every file inside it but leaves each row’s identity alone — it has to, because that identity is what links, tags and properties hang off. Except the identity is derived from the path. From that moment the rows are internally inconsistent, and the next refresh inserts a fresh row that collides with the one already there.

Two things then made it invisible. A full scan is written as one atomic batch, so a single bad row rolls the whole scan back. And every caller swallowed the error. The index stayed broken across restarts, which is why nothing the reporter tried made any difference — and why the deletion reported the opposite of what had happened: the folder was removed, the re-index right afterwards threw, and the tree was never refreshed.

It repairs itself on the next scan now. Nobody has to clean anything up.

What I did not do

Three things I was asked for and declined, because saying so is more useful than quietly not doing them.

I did not cap upload sizes. I did not lower the engine requirement on the Mac that showed a white window — the guard now names which capability it found missing and prints the user agent, because lowering a floor on a hunch is how you ship an app that starts and then fails somewhere less obvious. And I did not raise the default sync interval from 15 to 300 seconds, which had been suggested as a fix for the freeze: overlapping cycles cannot happen — a cycle already running returns immediately — so a slower default would have hidden a symptom whose cause was the memory problem above.

Also in this release

Encrypted workspaces can be decommissioned, handed over and have devices revoked from the phone (still experimental — the cryptography has not had an independent review). Connecting several services of one account on mobile needs fewer consent prompts, because the first one of a run covers several services at once, and the provider you tap is finally the one whose form opens. The calendar pulls only what changed where the provider offers a change feed. A dead calendar sign-in stops costing a network round every two minutes. And http:// servers on a home network are reachable on Android, which they were not — the release build kept the platform’s https-only default.

Plainva stays what it is: your notes as plain Markdown files, on your disk, in folders you chose. Local-first, AGPL, no account needed.