Terrain Viewer
Dev

Desktop App (Electrobun)

The web app packaged as a desktop application with Electrobun - what the build produces, what launcher.exe and cottontail.exe are, the portable (no installer) path, icons, docs and fullscreen

The desktop app is the same Vite build as the website, shown in the operating system's own webview (WebView2 on Windows, WKWebView on macOS, WebKitGTK on Linux) by Electrobun. No Chromium is bundled, so a build is about 36 MB. The point is working offline on local data: a COG picked from disk opens through the app's own file picker and every mode runs in the webview; online sources (Mapterhorn, basemaps, WMS services) still need the network. It is an experiment: unsigned, not released, built on demand by the Desktop bundles GitHub workflow (manual trigger) on a macOS, a Windows and an Ubuntu runner. Sources live in desktop/ of the repository.

The pieces

FileWhat it is
HutchElectrobun's command-line tool (hutch electrobun build, hutch electrobun dev). It installs the Electrobun devkit for the version pinned in desktop/hutch.config.ts (2.0.2) into desktop/.hutch/, the way rustup or corepack pin a toolchain, and drives the build. It is Electrobun's own CLI since v2, not a fork.
cottontail.exeThe main-process runtime. Electrobun 2 runs the main process (the TypeScript in desktop/src/bun/index.ts, which opens the window) on Cottontail, its own trimmed Bun-derived runtime, instead of a full Bun binary: mainProcess: "cottontail" in electrobun.config.ts.
launcher.exeThe small native bootstrap the shortcut points at. It locates the current app version (the updater can install a newer one next to it), starts Cottontail with the main script, and hosts the native window / webview through ElectrobunCore.dll.
bspatch.exe, zig-zstd.exeThe updater's tools: apply delta patches, decompress .tar.zst update archives.
Resources/app/views/app/The Vite dist/, served to the webview under the views://app/ scheme.

What a build writes

Hutch writes next to electrobun.config.ts (i.e. in desktop/), never into the app's dist/:

desktop/build/stable-win-x64/
  TerrainViewer/                      the application folder - runs as is
    bin/launcher.exe                  start here
    bin/cottontail.exe, ElectrobunCore.dll, ...
    Resources/app/views/app/          the web build
  Terrain Viewer-Setup.exe            installer
  stable-win-x64-TerrainViewer.tar.zst, stable-win-x64-update.json   updater feed
desktop/artifacts/
  win-x64-TerrainViewer-Setup.zip     what the workflow uploads
  terrain-viewer-portable-windows-x64.zip   the application folder, zipped (workflow step)

macOS gets TerrainViewer.app and a .dmg, Linux a folder and a self-extracting .tar.gz.

Portable: no installer

Electrobun does not produce a single-file executable: the webview host, the runtime and the web build are separate files by design (the updater patches them individually). The portable form is the application folder itself. The workflow zips it as terrain-viewer-portable-<platform>.zip: unpack anywhere, run bin/launcher.exe (TerrainViewer.app on macOS, bin/launcher on Linux). Nothing is written to the registry or to Program Files; the app's own data (the browser storage of the webview) goes to the user profile as any WebView2 app's does.

Building locally:

pnpm install && pnpm build                 # dist/
pnpm --dir docs install && pnpm run docs:build && mkdir -p dist/docs && cp -r docs/out/. dist/docs/   # optional: bundle the docs
cd desktop
node gen-config.mjs                        # electrobun.config.ts from dist/
hutch install                              # devkit, once
hutch electrobun build --env=stable

Two Windows gotchas, both in desktop/README.md: build from a plain folder (a git worktree path fails with AccessDenied), and from PowerShell rather than Git Bash (the release step runs tar; GNU tar fails, Windows' bsdtar works).

Icon

desktop/icons/ is public/favicon.svg rasterised: icon.ico (16 to 256 px) for the installer, the shortcut and the taskbar, icon.iconset/ for the .app (converted with iconutil on the macOS runner), icon.png (512 px) for the Linux desktop entry. gen-config.mjs sets build.win.icon, build.mac.icons and build.linux.icon. Hutch embeds the .ico into launcher.exe, cottontail.exe and the setup executable.

When dist/docs/index.html exists (the workflow merges the Next.js export there, as the Pages deploy does), gen-config.mjs bundles the docs and the sidebar's Documentation button resolves to views://app/docs/ offline. The main process handles new-window-open (links with target="_blank"): a views:// URL opens in a second window, an http(s) one in the system browser. Unverified so far: whether the views:// handler serves index.html for a directory URL (the docs export uses trailing-slash URLs); the fallback would be serving the docs from a local port.

Fullscreen

The map's fullscreen button uses the browser Fullscreen API on the map container. Inside WebView2 or WKWebView the element fills the webview; whether the native window frame goes away as well has not been checked. Electrobun's BrowserWindow.setFullScreen() is there if it does not, wired from the main process.

On this page