Skip to content

Desktop App

If you would rather not use a browser, or want something that works offline, there is a desktop build. It bundles the backend and frontend together — double-click and it runs, no network needed.

Download

Look for installers on this fork's Releases page and choose by system and chip. If your platform has no custom build yet, build custom-base as described below. Upstream installers do not represent this fork's features.

System File
Windows .exe installer (NSIS), or the portable exe
macOS (Apple Silicon) .dmg

In-app updates

The update dialog shows what changed. Choose “Update and restart” to download and verify the package, prepare recovery, save your draft, exit, install or replace the application, and reopen it. Active generation delays installation. Choosing “Later” or a failed save keeps the application open.

Windows per-user installations run the installer silently; portable updates retain the old EXE. macOS uses a complete application ZIP and atomic directory exchange. The new version must restore the workspace, render its interface, and connect to its backend before the update is confirmed. Startup failure triggers recovery of the previous version. User data is outside the replaced installation.

Protected or read-only installation locations, missing recovery packages or startup entries, and system trust requirements can require manual installation. macOS may still request system approval; the updater does not bypass it. Older versions without the native helper need a one-time manual installation of a helper-enabled version.

Only Apple Silicon Mac builds are published

There is currently only an arm64 macOS build; Intel Macs cannot install it. Windows builds are unaffected.

macOS blocks it on first launch

The desktop build has no Apple Developer signature or notarisation. Builds use a local ad-hoc integrity signature; the project has no Apple Developer certificate. macOS can therefore block the first launch. On macOS 15 and later the warning may say the file is "damaged" outright.

That warning has two possible meanings — work out which one you are looking at:

  • Most of the time it is the quarantine attribute: macOS tags every downloaded file with it, and an unsigned app trips over it on first launch. The file itself is fine.
  • It can also mean the file really is corrupted, or has been tampered with. Be especially wary of installers obtained from anywhere other than the official channel.

So first confirm you downloaded the installer from this repository's Releases page. If the source checks out, use one of the methods below to let it through. If you cannot verify where it came from, do not bypass the warning and do not open the app.

Once confirmed, run this once in a terminal:

xattr -dr com.apple.quarantine /Applications/HandwritingWeb.app

Or go to System Settings → Privacy & Security and click "Open Anyway".

Both of these tell macOS to stop blocking the app. They are necessary only because the project has no Apple Developer certificate to sign and notarise the build — they do not mean the build is safe. Hence the "verify the source first" step above is not boilerplate.

How it differs from the web version

The desktop build uses the same React frontend and Python rendering service. The main differences are:

  • Local generation — there is no fixed ten-page truncation, but memory, paper dimensions, and hardware still constrain rendering.
  • Error reporting disabled — the desktop runtime disables Sentry; the frontend does not load analytics or chat scripts.
  • No pandoc download — .docx files are handled by python-docx when pandoc is absent.
  • No overload guard interference — the CPU guard is relaxed so the app cannot rate-limit itself.

Where your data lives

The task database, temporary files, logs and fonts live in the user data directory. Deleting it is equivalent to a factory reset:

System Location
Windows %LOCALAPPDATA%\HandwritingWeb
macOS ~/Library/Application Support/HandwritingWeb

Roughly:

  • tasks.db — the task queue (SQLite)
  • temp/ — rendered images
  • logs/ — logs
  • fonts
  • studio/ — editor drafts, presets, and uploaded assets, independent of backend task expiry

Building it yourself

The build must run on the target platform — PyInstaller cannot cross-compile, so Windows packages can only be produced on Windows and macOS packages only on a Mac of the matching architecture.

bash desktop/build.sh                 # full build: frontend → backend executable → installer
bash desktop/build.sh --app-only      # Electron directory only, no installer
PYTHON=python bash desktop/build.sh   # pick the interpreter (used by CI)

Artifacts land in desktop/build/. For development, run --app-only first, then npx electron . from desktop/.

Read AGENTS.md for development conventions and Architecture for persistence and saving before exit. Consult the corresponding source and tests before changing build, security, or platform behavior.

Attaching missing packages to an existing release

Manually run Desktop Release Assets in Actions with an existing tag and choose all, windows, or macos for platform. Choose macos when only Mac packages are missing, so published Windows packages are not rebuilt and overwritten. Application source, dependencies, and packaging settings always come from the selected tag. Uploads require the complete desktop E2E suite to pass.

Normally leave test_harness_sha empty to use the tag's tests. If a historical release is blocked solely by an incorrect platform assertion in the update test, supply the full 40-character corrective commit SHA. It must be the workflow commit itself or its ancestor. After building, only e2e/desktop-tests/updates.spec.js is replaced; other application and test files remain unchanged. Logs record both the application source and test SHA. This neither moves the tag nor creates a version.

gh workflow run desktop_release.yml --ref custom-base -f tag=v1.7.1 -f platform=macos -f test_harness_sha=<full-corrective-commit-SHA>