Contribute
Building the app
How to run Ovillo's Electron shell while you work on it, build an unsigned Mac app for your own use, and what a signed release involves. Read Contributing first for setup and the data rules.
Running the desktop shell in development
desktop/ is an Electron shell around the same local server. It starts server.mjs in a utility process on 127.0.0.1 (the first free port from 47173), opens it in a locked-down window and stops it when the window closes. Electron lives only in desktop/, not in the root package.json.
- Build
vendor/from the repository root if you have not:npm run assets. - Install the shell's dependencies:
npm --prefix desktop install. - Start it with a temporary data folder:
HP_DATA_DIR="$(mktemp -d)" HP_NO_UPDATES=1 npm --prefix desktop start. It runs from the repository's own files, with no staging step.
Important. The desktop shell defaults to ~/.health-picture, where a real vault lives. In development always set HP_DATA_DIR to a temporary folder, and set HP_NO_UPDATES=1 so a development copy does not check for updates.
Smoke test
desktop/test-smoke.mjs launches the app against a temporary data folder and a separate profile, then checks the shell's guarantees: the window flags (context isolation on, Node integration off, sandbox on), that an outside fetch is blocked, that camera and location are not granted, and, in a packaged arm64 build, that the reader helper reads one synthetic PDF.
cd desktop
HP_DESKTOP_DEV=1 node test-smoke.mjs # development, runs electron .
HP_SMOKE_APP=/path/to/Ovillo node test-smoke.mjs # a packaged binary
Without either variable it uses a built app in desktop/dist/ if there is one, and otherwise runs electron ..
Building an unsigned app for yourself
This makes desktop/dist/mac-arm64/Ovillo.app for Apple Silicon, signed only ad hoc, which is enough to run on the Mac that built it. It is not notarised, so another Mac will warn about it.
npm run assets
npm --prefix desktop install
HP_NO_HELPER=1 npm --prefix desktop run dist:mac:unsigned
HP_NO_HELPER=1 is needed because the build checks that an Apple Silicon bundle carries the optional reader helper, and the helper is not in this repository (see below). Without the helper the app still reads PDFs, photos and scans with its built-in readers. It loses only the extras that need the helper, such as on-device dictation and page translation.
The packaging step runs node stage.mjs first. It copies the page files, server.mjs, src/ and vendor/ (about 190 MB of models and WebAssembly) into desktop/stage/. A check after packing fails the build if the bundle contains a symlink, or a .env file, tests or fixtures.
Run the smoke test on the result with HP_SMOKE_APP pointing at the binary inside the app.
Adding a new top-level file
Files under src/ are picked up automatically: the server serves any src/*.mjs, and staging copies the folder. A new file at the top level is not. Staging copies a fixed list, and the server serves a fixed allowlist. If you add one (as viewer.html was), add it in both places:
- the file list in
desktop/stage.mjs, or the packaged app will not contain it, and TOP_FILESinserver.mjs, or the server will refuse to serve it.
Miss either and the page works from source but is broken in the packaged app.
The signed release, for maintainers
You do not need this section to contribute. It describes what releasing involves so the process is not a mystery. You would need your own Developer ID certificate and notarisation profile to repeat it, and a signed build from anyone else is a different app.
- Bump
versionindesktop/package.json. Every build gets a new version, because installed apps compare it against the published one. - Build the reader helper separately. It is a Swift program called offload that lives in its own checkout, not in this repository. Build it in release mode there.
- Fetch it with
node fetch-offload.mjsindesktop/. This copies the binary into the git-ignoreddesktop/helper/arm64/. Only the Apple Silicon app ships it. It is signed during packaging with a sandbox entitlement and no network access. Intel builds go without it. - Stage a clean output folder and run electron-builder with your Developer ID and notarisation profile set in the environment, and with
--publish never. It makes a dmg and a zip for each of arm64 and x64, their blockmaps andlatest-mac.yml, signs the app, and notarises and staples the app and each dmg. Notarisation needs your keychain profile, so run it outside any sandbox. - Verify with
spctl -a -vvon the app: it should say "accepted, source=Notarized Developer ID". - Publish a GitHub release in the ovillo-releases repository, tagged
v<version>, uploadinglatest-mac.yml, both zips, their blockmaps and both dmgs, plus copies of the dmgs namedOvillo-AppleSilicon.dmgandOvillo-Intel.dmgso the website's download links always reach the newest version. That repository holds installers and update files only, no source.
The step-by-step certificate and notarisation setup is in desktop/README.md.
How installed apps update
The packaged Mac app checks github.com/BraedenBDev/ovillo-releases at launch and every six hours, from the shell's main process. It downloads a newer release in the background, offers Restart now or Later, and otherwise installs when Ovillo closes. macOS accepts an update only if it is signed with the same Developer ID. GitHub sees the app's version and the computer's IP address, and nothing from the record. HP_NO_UPDATES=1 turns the check off; the smoke test sets it.
Windows
A dist:win script exists and builds an unsigned NSIS installer for x64. No Windows build is published, and it does not update itself. Ovillo is Mac only for now. Code signing for Windows (the free SignPath Foundation programme for open-source projects is the option noted in the config) is not set up.
Something unclear or out of date? Tell us on GitHub.