A task manager is something you touch dozens of times a day. If every tick, rename and drag has to wait for a server, the waiting adds up, and on a train or in a basement it stops you altogether. So we built thisflow the other way round: your tasks live on your device first, and the network catches up in the background. This post explains what that means in practice, and what it costs.

What local-first means here

When you open thisflow, the app keeps a real database on your device. In the browser it is SQLite (running as WebAssembly and stored in IndexedDB), one database per signed-in person. Every list, board and calendar reads from that local database. Search runs on it too, which is why results appear as you type.

On the server we run PostgreSQL. Between the two sits PowerSync, a sync engine that replicates the rows you are allowed to see from Postgres into the SQLite database on each of your devices. A client only receives rows from projects it has a role in, so a guest in one project never receives anything from the rest of the workspace.

Writes are named commands

When you change something, the app does three things in one local transaction: it updates what you see, writes the change to the local rows, and queues a named command such as createTask, toggleComplete or moveToSection. The queue is uploaded to our API in order, oldest first, whenever there is a connection.

We send commands rather than raw row changes because commands keep their intent. The server validates each one, checks your role in that project, writes it in a transaction and records it in the activity log. The same commands drive undo, task history, the AI features and the MCP server, so there is one way to change a task, wherever the change comes from.

Each command also carries its own id, so a batch that is retried after a dropped connection is recognised and not applied twice.

Instant, measured

One of our product principles is that every interaction shows its result in under 50 ms. Because reads and writes go to the local database, the network is never on that path.

We check this rather than assume it. Our performance test runs against a project with 1000 tasks, and in the release we called "Instant in long lists too" completing, opening and quick add measured about 16 to 48 ms, down from up to 100 ms before (changelog). They come from our test machine, not from your device.

Offline is the normal case, not an exception

Before choosing PowerSync we compared it with another sync engine in a small, repeatable test. The other engine was faster on the web in every number we measured. It lost on one thing: after 60 seconds without a connection it refused new writes, and in our long-offline test it kept 0 of 5 edits, where PowerSync kept 5 of 5. For a task manager that settled it. Capture first, organise later only works if capture never depends on the network.

In thisflow, changes made offline are kept on the device, survive a reload, and upload when the connection returns. The sidebar tells you which state you are in: Offline when the device has no network, Not connected when it has one but cannot reach us.

Conflicts, resolved per field

When two people change the same task, thisflow resolves it per field, and the last write wins. If you change a due date while a teammate renames the task, both changes are kept, because they touch different fields. If you both change the same field, the later change wins.

Undo follows the same rule. Undoing an edit writes back only the fields that edit changed, so a teammate's edit to another field in the meantime is kept.

The trade-offs, honestly

Local-first is not free, and we would rather say so.

  • You are told when your change did not stick. If someone's newer change wins over yours, the app says that their newer change was kept. If the server refuses a change, for example because you no longer have access to that project, the change is undone on your device and you are told why. If changes cannot reach the server for a while, the sidebar shows how many are waiting, with Retry now.
  • Your tasks take space on your device. The local database holds the projects you can see. The first visit also downloads the SQLite engine itself; after that the app shell is cached.
  • Signing out deletes the local copy, including changes not yet uploaded. The app warns you about unsent changes first. An expired session does not delete anything: queued changes upload after you sign in again.
  • Some things need the server. Task history is built from the server's activity log, so it needs a connection. Sharing and membership changes are server-only too, because they decide what the sync engine sends.
  • Last write wins is simple, not magic. Two people editing the same description at the same moment do not get a merged text. We will add collaborative text editing only if people ask for it.

Where the data lives

The server side runs in the EU. Postgres and the PowerSync service are self-hosted on our server at UpCloud in Stockholm. The copy on your device is yours: it lives in your browser's storage and is removed when you sign out. More on how we handle data is on our security page.

Try it

The quickest way to feel the difference is to add a few tasks and switch off your network. The getting started guide takes about five minutes, and quick add works the same online and offline.