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 without the documentation is about 40 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, built by the Desktop bundles GitHub workflow, on demand only since 2026-10-03 (the weekly schedule and the macOS and Linux jobs are paused to save Actions minutes, so bundles are built locally for now; every platform's last bundle stays on the release), and attached to the rolling desktop-latest release, which is replaced on every run. Each platform comes in two files, TerrainViewer-with-offline-docs-v<date>-Setup-<platform> (the documentation bundled for offline reading, about 220 MB) and TerrainViewer-light-online-docs-v<date>-Setup-<platform> (about 40 MB, its Documentation button opens this website); Windows and Linux also get a ...-Portable-<platform>.zip that runs without installing. The date is the build day. Sources live in desktop/ of the repository.
Installing or running on Windows and Linux
On Windows the Setup file is one self-contained .exe (the payload is appended to Electrobun's installer stub, the layout its Linux installer uses; Hutch itself only writes a zip with a hidden .installer folder beside the exe). It installs under %LOCALAPPDATA%\com.iconem.terrain-viewer (-light for the light build, so both can be installed) and adds a Start menu entry; the light build's shortcuts are named "Terrain Viewer Light" so the two installs do not overwrite each other's. Links the app opens in a new tab (the documentation, GitHub, data providers) go to the system browser: the page sends them to the main process over the host channel, because on Windows Electrobun's WebView2 wrapper has no new-window handler and a plain window.open would pop WebView2's own bare window. On Linux the Setup is a self-extracting tar.gz.
The Portable zip needs no installation: unzip it anywhere and start Terrain Viewer.cmd (Windows) or ./terrain-viewer (Linux); the program itself is bin/launcher, which has to stay next to Resources/. Nothing is written outside the folder except the webview's own profile.
Updates
Two rolling releases, one per build: desktop-latest holds the full build (installers, portable zips and its updater feed), desktop-latest-light the light build's. Electrobun names the feed after the channel (stable-<platform>-update.json), so the two builds cannot share one release. Both check their feed at every launch. The full build, in detail: stable-<platform>-update.json and the matching .tar.zst are uploaded next to the installers, and Electrobun's updater (release.baseUrl in the generated config) downloads a newer build in the background (a toast says so when the download starts: about 220 MB, a few minutes), shows a toast with Restart now once it is ready, and otherwise installs it at the following launch, quitting and relaunching through its update helper, which takes about half a minute. The About section shows the updater's state under the version line: checking, downloading with its percentage, downloaded and waiting for the next launch, installing, installed at this launch. Every step is also logged to updater.log next to the app folder (%LOCALAPPDATA%\com.iconem.terrain-viewer\stable on Windows). Builds made before 2026-10-02 have no feed address baked in and never update: install the current one once. The About section at the bottom of the sidebar shows the build day and commit; its Is this the latest version? link compares that commit with the newest commit on main and with the commit the desktop-latest bundles were built from, and links the release page. The light build has no feed and is replaced by downloading it again; so is the Portable folder, which the updater never touches (it only knows the installed copy).
Opening the macOS build
The app is not signed with an Apple certificate, only ad-hoc signed on the build runner, so macOS refuses it on first launch. Either right-click the app and choose Open, then confirm; or, if it is reported as "damaged", clear the download quarantine flag in Terminal and open it normally:
xattr -cr "/Applications/Terrain Viewer.app"Windows shows a SmartScreen warning for the same reason: More info, then Run anyway.
The pieces
| File | What it is |
|---|---|
| Hutch | Electrobun'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.exe | The 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.exe | The 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.exe | The 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=stableTwo 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.
Docs and external links
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.