Desktop app: Tauri shell, self-contained packaging, CI releases + self-update - #42
Merged
Conversation
Run the data pipeline as in-process services and resolve all on-disk state through a central data dir, so the app can run from a packaged build with no dev tooling on the machine. - paths.ts resolves DATA_DIR / RESOURCE_DIR (env-overridable, PYOPS_DATA_DIR etc.) and the derived paths; nothing joins off process.cwd() directly - provision.ts applies the bundled drizzle/ migrations in-process on first connect, replacing the drizzle-kit push child process and the ad-hoc ensureCoreUpgrades ALTERs; the baseline migration is idempotent so existing dbs adopt it cleanly - icon-atlas.ts builds the atlas in-process (was a spawned script); dump.ts uses top-level imports instead of inline await import() - serve /icons/* from the data dir in production (routes/icons.$.ts), with immutable caching for fingerprinted sheet URLs - detect a running Factorio cross-platform via pgrep/tasklist instead of flock - vp run db:generate requires a migration name (scripts/db-generate.mjs) - drop the obsolete dev scripts (icon-atlas builder, atlas preview, reload helper)
Add a Storage location card (Settings -> Game data) listing the resolved data dir, project database, icon atlas, and config paths, each copy-able, so the location is discoverable when sharing a database or filing a bug report.
Update AGENTS.md, architecture.md, and data-pipeline.md for the data dir, the in-process schema provisioning via drizzle migrations, the in-process icon atlas, and the trimmed scripts/ and db scripts.
Scaffold app/src-tauri. The dev flow boots the Nitro server bundle via beforeDevCommand and loads it in a webview window pointed at the local server (devUrl). On Linux it forces the X11 backend and disables webkit's DMABUF renderer to avoid the Wayland "Error 71" protocol error. Vendoring a node runtime + the server as bundled resources for a standalone build comes next.
The bundled build starts the Nitro server via a vendored `node` sidecar (externalBin) against the bundled `.output`, drizzle migrations, and mod source (bundle resources), with state in the per-OS app-data dir. The window opens hidden and reveals on first paint, so there's no blank webview while the server boots. Dev still starts the server via beforeDevCommand. vendor-node.sh fetches the node binary, which is gitignored (per-platform, ~80-120MB). Verified: the .deb builds and the bundled binary runs with no system node — the sidecar boots the server and provisions a fresh db in the app-data dir. AppImage is disabled for now (linuxdeploy fails to bundle GTK on this host).
Tag-triggered (v*) workflow that builds the Tauri bundle on a per-platform runner and attaches the artifacts to a draft GitHub Release. Mirrors ci.yml's voidzero-dev/setup-vp setup, adds the Rust toolchain + Linux webview deps, and runs vendor-node.sh to fetch the sidecar (gitignored) before building. Scoped to Linux (deb + appimage) for now — the matrix is structured so macOS/Windows are a few added lines. The Ubuntu runner also builds a working AppImage, which the Arch dev host's linuxdeploy can't.
Add macOS (arm64 + Intel) and Windows to the release matrix, so a tag builds deb/AppImage, dmg, and an NSIS installer in one go. Run the vendor + build steps under bash for a uniform invocation across runners, and make vendor-node.sh's Windows zip extraction fall back across unzip/7z/tar. macOS and Windows bundles are unsigned for now (Gatekeeper/SmartScreen will warn) — signing is a later add.
Show "PyOps v<version>" (from tauri.conf.json) in the title bar and regenerate the app icons from the real logo. Open the first run at 1800x1100 so the desktop nav shows even with fractional display scaling (the inline bar collapses to a hamburger below ~1400 CSS px — which the Steam Deck still gets intentionally), and persist + restore the window size/position across launches via tauri-plugin-window-state.
Keep the window pinned to the local server: a navigation hook sends any non-localhost http(s) URL to the default browser (tauri-plugin-opener) and cancels the in-app navigation, and an init script routes target=_blank link clicks through that same hook. So the GitHub button — and any future external link — opens in the browser instead of hijacking the app window.
Conventional commits now drive the version bump and changelog: release-please (release-please-config.json + manifest) bumps the version across app/package.json, Cargo.toml, and tauri.conf.json in lockstep. The release workflow runs on main — release-please maintains a "release PR", and merging it cuts the GitHub release + tag, which gates the per-platform build that attaches the bundles to it. No PAT needed (build runs as a dependent job in the same workflow). Activates once this branch merges to main.
release-type node bumps app/package.json, but it had no version field — so it would have been skipped and drifted out of sync with Cargo.toml / tauri.conf.json. Seed it at 0.1.0 to match.
…pdate Enable createUpdaterArtifacts and configure the updater plugin (public key + GitHub latest.json endpoint). The release build now goes through tauri-action, which signs the updater artifacts with the CI keypair, attaches them to the release-please release, and aggregates the multi-platform latest.json the app checks against. Self-update rides AppImage / NSIS / .app (not the deb). The in-app check + prompt is the next step.
On a bundled build, check GitHub for a newer release in the background and, if one is available, show a native "Update available" dialog with the version and changelog (from latest.json's notes) plus Install & Restart / Later. Accepting downloads, installs, and relaunches the app. Entirely Rust-side (tauri-plugin- updater + tauri-plugin-dialog), so the web UI stays Tauri-agnostic; dev skips it.
Add docs/desktop.md (the Tauri shell, building a bundle, the release-please pipeline, and self-update) and link it from the docs index. Note the desktop shell + the release-please-managed version in AGENTS.md, add src-tauri/ and drizzle/ to the architecture tree, and add a user-facing "Desktop app" section with download links to the README.
# Conflicts: # AGENTS.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Turns PyOps into a self-contained desktop app and automates its release pipeline. The web app is unchanged in spirit — Tauri wraps the existing Nitro server; the React UI stays Tauri-agnostic.
What's here
Self-contained app
server/paths.ts).Desktop shell (
app/src-tauri)beforeDevCommand, bundled via a vendorednodesidecar against bundled.output+drizzle+modresources, with state in the per-OS app-data dir.CI / releases
.deb+.AppImage,.dmg(arm64 + x64), and an NSIS installer.latest.json, and an in-app 'Update available' prompt that shows the changelog and installs on accept.Validation notes
mainand a release runs — expect possible first-run tweaks (tauri-action +vpis the least-proven bit). The build matrix itself was proven green on a throwaway tag.Follow-ups