Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
ff264dd
Desktop shell: engine detection, env generation, stack lifecycle
davidmckayv Sep 5, 2026
f66e7c9
Desktop shell: engine acquisition and the Windows restart
davidmckayv Sep 5, 2026
267be0b
Desktop shell: the window, the wizard, and the commands behind it
davidmckayv Sep 5, 2026
10d1482
Build the desktop shell on all three platforms in CI
davidmckayv Sep 5, 2026
d41b72b
Desktop shell: fetch the deployment, and mean it when it says running
davidmckayv Sep 5, 2026
89e0ba9
Desktop shell: single user, the shared tsconfig, and not trusting a port
davidmckayv Sep 5, 2026
01c205b
Desktop shell: the settings a deployment on one machine needs
davidmckayv Sep 5, 2026
5a8fc56
Desktop shell: the menu bar, the open button, and a window opened twice
davidmckayv Sep 5, 2026
03d6528
Desktop shell: the window is OpenBot, not a launcher for it
davidmckayv Sep 5, 2026
9ffeb68
Address the engine by name, and run the images the release published
davidmckayv Sep 5, 2026
1ed87ac
Make the window survive being closed, and stop what starting started
davidmckayv Sep 5, 2026
798a16e
Stop in seconds, and say so when it fails
davidmckayv Sep 5, 2026
e473181
Supervise the host processes, and stop shipping generated files
davidmckayv Sep 5, 2026
1747503
Pin rust-cache to a tag that exists
davidmckayv Sep 5, 2026
66930fc
Give the Windows build the icon it cannot build without
davidmckayv Sep 5, 2026
90a5cd2
One watcher per run, so a quick restart does not double the processes
davidmckayv Sep 5, 2026
f58f96a
Do not tell somebody to switch on virtualization that is already on
davidmckayv Sep 5, 2026
4e29b2f
Build the desktop app the way it ships, not the way it compiles
davidmckayv Sep 5, 2026
1e5215c
Say what to install when the engine has no Compose
davidmckayv Sep 5, 2026
66fa7c5
Install the deployment's dependencies without running other people's …
davidmckayv Sep 6, 2026
5c5be64
Run the app through bun, so a machine without Node can still start it
davidmckayv Sep 6, 2026
e296f0f
Serve the app as a build, so an install is not running a development …
davidmckayv Sep 6, 2026
cfc5ce1
Name a deployment that is older than the app, rather than restarting …
davidmckayv Sep 6, 2026
060fbf0
Say when the window could not become OpenBot
davidmckayv Sep 6, 2026
0c761cb
Keep the installers CI builds
davidmckayv Sep 6, 2026
31c1379
Stop the Windows blocker screen offering to do something nothing does
davidmckayv Sep 6, 2026
05715a0
Stop promising to install an engine that nothing installs
davidmckayv Sep 6, 2026
2497e47
Stop the Windows build flashing consoles, offering Save as, and stran…
davidmckayv Sep 6, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
110 changes: 110 additions & 0 deletions .github/workflows/desktop.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
name: Desktop

# The shell is the one thing here that cannot be proved by running it on Linux: it is a macOS app, a
# Windows app and a Linux app built from one tree, and the ways they differ are exactly the ways
# this fails. So it builds on all three, on every change to it.
on:
pull_request:
paths: ["desktop/**", ".github/workflows/desktop.yml"]
push:
branches: [main]
paths: ["desktop/**", ".github/workflows/desktop.yml"]
workflow_call:

permissions:
contents: read

concurrency:
group: desktop-${{ github.ref }}
cancel-in-progress: ${{ github.ref != 'refs/heads/main' }}

jobs:
# Fast, and the only job that runs the assertions about what the shell writes into `.env` and
# which socket it names. Those are the parts that carry what the platforms taught us, and they are
# plain Rust: no window, no engine, no waiting.
core:
name: core
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: dtolnay/rust-toolchain@6bed0761d98439e5a578e2877258200ad565ba87 # stable
with:
toolchain: stable
components: rustfmt, clippy
- uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2
with:
workspaces: desktop/src-tauri
# Linux is the only target needing system libraries, and it needs them even to check.
- run: |
sudo apt-get update -qq
sudo apt-get install -y -qq libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf
- run: cargo fmt --check
working-directory: desktop/src-tauri
- run: cargo clippy --all-targets -- -D warnings
working-directory: desktop/src-tauri
- run: cargo test --lib
working-directory: desktop/src-tauri

# The three artifacts. Not `bundle`, which signs and notarizes: that is S7 and needs certificates
# this workflow deliberately does not hold. This proves the tree builds into an app on each
# platform, which is the thing that breaks when a dependency is not portable.
app:
name: app (${{ matrix.platform.name }})
runs-on: ${{ matrix.platform.runner }}
strategy:
fail-fast: false
matrix:
platform:
- name: macos
runner: macos-latest
- name: windows
runner: windows-latest
- name: linux
runner: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0
with:
bun-version: 1.3.14
- uses: dtolnay/rust-toolchain@6bed0761d98439e5a578e2877258200ad565ba87 # stable
with:
toolchain: stable
- uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2
with:
workspaces: desktop/src-tauri
key: ${{ matrix.platform.name }}
- if: matrix.platform.name == 'linux'
run: |
sudo apt-get update -qq
sudo apt-get install -y -qq libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf
- run: bun install --frozen-lockfile
working-directory: desktop
- run: bun run typecheck
working-directory: desktop
# `tauri build`, not `cargo build --release`. tauri-build emits `cargo:rustc-cfg=dev`
# for anything the Tauri CLI did not build, so a plain cargo release binary still points
# at the dev server and shows "Could not connect to localhost" when it is run. That
# compiles, proves the Rust, and proves nothing about the thing people install.
#
# Bundling is included because the bundle is the product, and because the failures live
# there: the Windows resource step needs an .ico, and macOS wants an .icns, neither of
# which a compile would miss.
- run: bun run tauri build
working-directory: desktop
# Keep what was built. Without this the only way to try an installer is to build one on
# the machine you are trying it on, which is not what anybody installs.
- uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
with:
name: openbot-desktop-${{ matrix.platform.name }}
path: |
desktop/src-tauri/target/release/bundle/**/*.dmg
desktop/src-tauri/target/release/bundle/**/*.exe
desktop/src-tauri/target/release/bundle/**/*.AppImage
desktop/src-tauri/target/release/bundle/**/*.deb
desktop/src-tauri/target/release/bundle/**/*.rpm
if-no-files-found: error
retention-days: 7
5 changes: 3 additions & 2 deletions app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@
"version": "0.0.0",
"type": "module",
"scripts": {
"build": "vite build",
"dev": "vite",
"build": "bun --bun node_modules/vite/bin/vite.js build",
"dev": "bun --bun node_modules/vite/bin/vite.js",
"serve": "bun run build && bun --bun node_modules/vite/bin/vite.js preview",
"prebuild": "bun run --cwd .. generate:app-config",
"predev": "bun run --cwd .. generate:app-config",
"pretypecheck": "bun run --cwd .. generate:app-config",
Expand Down
39 changes: 27 additions & 12 deletions app/vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,38 @@ import { tanstackRouter } from "@tanstack/router-plugin/vite";
import react from "@vitejs/plugin-react";
import { defineConfig } from "vite";

/*
* The same address and the same proxy whether this is the dev server or the preview of a build.
*
* `preview` is a separate config key with its own defaults, so a deployment that serves the built
* app rather than the dev server gets no `/api` proxy unless it is repeated here. A desktop install
* serves the build, and without this every call it makes returns the app's own HTML.
*/
const serving = {
// Both loopbacks, which is what `::` gets you: Node opens a dual-stack socket, so 127.0.0.1 and
// ::1 both answer. Left to itself Vite binds whichever one this runtime resolves `localhost`
// to, which is ::1 under Node and 127.0.0.1 under bun, and the other address is then refused.
// Whoever is told the URL has no way to know which they were given.
host: "::",
port: Number.parseInt(process.env.APP_PORT ?? "3010", 10),
strictPort: true,
proxy: {
// `ws: true` is required for the live screen. Without it Vite answers the upgrade request with
// the app's HTML and the socket fails with an opaque error that looks like a server problem.
"/api": {
target: `http://localhost:${process.env.SERVER_PORT ?? "3001"}`,
ws: true,
},
},
};

export default defineConfig({
plugins: [tanstackRouter(), react(), tailwindcss()],
resolve: {
alias: {
"@": path.resolve(__dirname, "./src"),
},
},
server: {
port: Number.parseInt(process.env.APP_PORT ?? "3010", 10),
strictPort: true,
proxy: {
// `ws: true` is required for the live screen. Without it Vite answers the upgrade request with
// the app's HTML and the socket fails with an opaque error that looks like a server problem.
"/api": {
target: `http://localhost:${process.env.SERVER_PORT ?? "3001"}`,
ws: true,
},
},
},
server: serving,
preview: serving,
});
2 changes: 2 additions & 0 deletions biome.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
"!app/src/components/ui",
"!app/src/lib/generated/application-config.ts",
"!app/src/routeTree.gen.ts",
"!desktop/src-tauri/gen",
"!**/target",
"!server/drizzle"
]
},
Expand Down
7 changes: 7 additions & 0 deletions desktop/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
dist/
node_modules/
target/

# Written by Tauri from the capabilities and the plugins in Cargo.toml. Build output that
# happens to be JSON, so it is regenerated on every build and belongs to nobody to review.
src-tauri/gen/
Loading
Loading