Why we built a Rust-powered desktop app for previewing documentation
Tags: rust, writing, javascript, webdev
Published: 2023-02-02 14:25:36

_This post was originally published on the [Doctave blog](https://www.doctave.com/blog/2023/02/02/why-we-built-a-rust-powered-desktop-app-for-previewing-documentation.html)._

---

We took the arguably unusual choice to build a desktop app for documentation. If you look around, documentation tools generally fall in 2 categories: open source CLI tools, and cloud-based WYSIWYG editors. In this post we'll talk about the reasoning that lead us to build a desktop app instead, and how it actually works.

## What is it for?

Our desktop app allows users to preview what their documentation will look like once published. It also catches errors such as broken links or syntactical problems.

Users can open our app next to their editor of choice, edit their Markdown documents, and the app will refresh on every save to reflect the latest version.

![an animation of the Doctave app updating the content shown when an edit is made in a text editor](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/y7ubzx3yi0a9nn50lyub.gif)


When an error is detected, the user is immediately notified, and clicking on the link in the error takes you to the page with the issue.

![a cropped screenshot of the desktop app showing an error notifying the user about a broken link](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/sv2g8hxzrx0nz57qq007.png)

While my opinion is obviously biased, this fast feedback loop is amazing to work in. It takes the app milliseconds to do a complete re-render of the project, so you're able to move essentially as fast as you type.

## Why a desktop app?

Doctave is built with
[docs-as-code](/blog/2021/08/30/why-you-should-consider-docs-as-code.html)
in mind. As said above, there are two popular approaches for documentation tools: CLIs and cloud-based WYSIWYG editors. The point of docs-as-code is working on local files in source control so the cloud was out immediately.

So this leaves the CLI option. There's nothing _"technically"_ that would have stopped Doctave from having this interface:

```bash
$ doctave serve .
Serving your docs at http://localhost:9090
...
```

But here are the negatives for requiring a CLI.

### Better UX

There has been a resurgence of desktop applications in the past few years. This is likely partly due to technologies like Electron making it easier to ship applications built with web technologies as cross-platform desktop apps, but it's also [arguably](https://www.youtube.com/watch?v=1vBesOFURek)
because the user experience can be better.

Our users love that the preview and error checking happen in one place. You don't have to spin up terminal sessions and swap between an editor, a terminal, and a browser.

Also, our app is fast. We are using [Tauri](https://tauri.app/) for the shell of our app and the business logic that compiles your documentation is written in Rust. It runs natively inside Tauri, and not in the browser environment (more on this below). This is how we can get such fast response times: we run native code instead of a bundle of JavaScript or even WebAssembly. Would it be possible to do this in a CLI? Yes, but the desktop app gives us a great environment to take advantage both native code and web technologies.

Finally, updates become trivial. Tauri's built-in update function has worked flawlessly, across 3 operating systems, ensuring our users have the latest and greatest version with minimal headache. You get a pop-up every time there's a new version, and the update happens automatically. There isn't really a universal way to update CLIs.

### Non-developer users

A lot of people working with technical documentation are not developers themselves. They are still technical, but in roles like tech writers, product managers, or support agents. They are comfortable on the command line, can use Git, but do not want to spend time understanding why their version of OpenSSL does not match what is expected.

If you are expecting your users to setup a complex development environment to run your tool, you are alienating this important group of users. Even developers get frustrated when small environment differences cause `npm install` to fail!

There are ways to mitigate this, such as building static binaries. But you still have the problem of getting your users to update your SDK when you release features, you have to support different package managers, you have to make sure your software is not flagged by the OS as malicious, and much more.

A common comment we hear from our users: it's just easier to use Doctave.

## How does it work?

As mentioned above, Doctave uses [Tauri](https://tauri.app/) as the shell of our application. Think of it as a lightweight and security-focused Electron. It's a very interesting project that I highly recommend checking out. For our purposes, here are the 2 key features that are interesting:

* It uses each operating system's native web view, making app bundles
  significantly smaller
* You can code your local "backend" in Rust which you can invoke from inside
  the web view

  _(NOTE: this is not a "web server backend". This is code running natively
  inside the local Tauri process.)_

Doctave's core documentation project parsing code is written in Rust and we call it `libdoctave`. It takes as input a bunch of files and creates an in-memory structure that you can use to render a Doctave documentation site. It is used both in the desktop app and on our web platform, written in Elixir, via [Rustler](https://github.com/rusterlium/rustler).

This is how we are able to share code across the desktop and the web platform. We can render the same content identically regardless of where we are.

### Rendering a page

As an example, let's walk through what happens when Doctave renders a page you are editing.

First, Doctave is running a thread inside our local "backend" that is monitoring filesystem events. If it detects a change, it triggers a refresh of the project. Here's how that works:

1. The backend re-reads _all_ the project files from disk
2. It feeds those files to `libdoctave` to create an in-memory representation of the project
3. `libdoctave` first runs a `build` stage, to check the project is syntactically correct
4. `libdoctave` next runs a `verify` stage, where all pages are checked for issues
5. Finally, the requested page's HTML is serialized and sent to the frontend to display, along with any errors

This all happens in single-digit milliseconds every time you edit a file.

As you can see, there is still a lot of room for optimization if ever required. We could be smarter about incrementally updating the `libdoctave` in-memory representation. We could reduce IO by only reading the files that have changed.

So far, however, that has not been necessary.

## Desktop apps rock 🤘

Betting on Tauri and having a desktop app in general has been a great decision. Especially with our choices of technologies, namely Rust, this combination has been a delightful if somewhat unconventional tech stack.

Every startup has a certain number of "innovation tokens" that you can use on risky and/or new technologies. In our case, this was a great investment.