Skip to content

Web Client (PWA)

The Hive app ships in three forms from one Vue codebase:

  • the desktop Tauri shell (Windows / Linux / macOS),
  • the mobile Tauri shell (Android), and
  • a standalone web client - the same frontend compiled to static files, loaded in a regular browser and installable as a PWA / iOS home-screen app.

This page covers the web client: how it is built, how it is deployed, and the one hard browser constraint you must satisfy for it to connect to your nodes.

How it works

The web build is produced with Vite's --mode web. In that mode the Tauri API modules (@tauri-apps/api/core, event, app, window) are aliased over the browser shims in crates/hive-app/src/browser-shim/, which talk directly to hived over a WebSocket - no Tauri runtime, no central backend. Connection profiles are persisted in localStorage.

vite-plugin-pwa adds a web manifest, icons, and a service worker so the app is installable. The service worker only precaches the app shell; it never touches the daemon WebSocket (service workers don't intercept ws:///wss:// traffic), so there is no offline session mode - a live daemon connection is always required.

The bundle is built with a base path of /app/ (override with VITE_WEB_BASE) so it can be hosted under a subpath such as https://hive.vazac.dev/app.

The HTTPS / wss:// requirement

A browser will not let a page served over https:// open a plain ws:// socket to a remote host - that is active mixed content, blocked by every modern browser, and there is no override on iOS Safari. The only ws:// target allowed from an HTTPS page is localhost.

Because the web client is served over HTTPS (required for a service worker / installable PWA), every node it connects to must serve wss:// - i.e. hived must terminate TLS. Enable it per node in the daemon config:

toml
# config.toml
tls_cert = "/path/to/fullchain.pem"
tls_key  = "/path/to/privkey.pem"

With both set, hived listens on wss://<addr>/ws instead of ws://. There is no manual "Use TLS" toggle in the connection form: the app auto-negotiates the scheme. On an HTTPS page only wss:// is possible (the browser forbids plain ws://), so it uses that; on an HTTP page it tries wss:///ws:// and falls back to whichever the node accepts, caching the working scheme per profile. Just enter the node address.

wss:// requires connecting by the node's Tailscale MagicDNS hostname (e.g. my-node.tailnet.ts.net), not a bare IP - the certificate is issued for that hostname, so an IP address fails TLS validation.

Because of this, the HTTPS web client steers you towards Tailscale *.ts.net domains: they are the no-DNS, no-paperwork path to a browser-trusted certificate. Any node reachable at its own public domain with a valid certificate works too - Tailscale is just the easy default. The connection form warns (but does not block) when the host you enter is neither a *.ts.net name nor localhost.

Getting a TLS domain for a node

Easiest - the Hive CLI. Run this on the machine the daemon runs on. It detects the node's Tailscale MagicDNS name, issues a cert with tailscale cert, writes it next to the daemon config, sets tls_cert/tls_key, and restarts the daemon:

bash
sudo hive cert --restart
# or, to issue for a specific name / skip the restart:
sudo hive cert --name my-node.tailnet.ts.net

hive cert reads the daemon's own config path automatically (and accepts an explicit --config <path> if hived runs with a non-default config). It fails fast with a clear message if Tailscale is not installed on the node.

Requirements: the Tailscale CLI on PATH (override the binary with HIVE_TAILSCALE_BIN for non-standard installs) and HTTPS Certificates enabled for the tailnet (Tailscale admin console -> DNS -> HTTPS Certificates). Tailscale certs expire after ~90 days - re-run hive cert to renew (then restart the daemon, since the cert is read once at startup).

Manual - the Tailscale CLI. Equivalent steps by hand:

bash
sudo tailscale cert \
  --cert-file /etc/hive/tls.crt \
  --key-file  /etc/hive/tls.key \
  my-node.tailnet.ts.net

then set tls_cert = "/etc/hive/tls.crt" / tls_key = "/etc/hive/tls.key" in the daemon config and restart hived. Connect the web client to my-node.tailnet.ts.net:9178 with TLS enabled.

If you would rather not run TLS on nodes, serve the app over http instead of https. Plain ws:// then works against every node. Served from http://localhost, the browser still treats it as a secure context, so the service worker / PWA install keep working too - you only lose those on a non-localhost HTTP origin (e.g. a LAN IP). See Run locally over HTTP below.

Run locally over HTTP

The simplest way to reach nodes that don't terminate TLS is to serve the web client yourself over plain HTTP on localhost:

bash
pwsh ./scripts/hive.ps1 serve        # build (root base) + serve on http://localhost:4173/

http://localhost is a browser secure context, so the service worker and PWA install still work - but because it is not an HTTPS page, the browser lets it open plain ws:// to any node. So you can connect to a node that has no tls_cert/tls_key set; just enter the node address (e.g. my-node.tailnet.ts.net:9178) and the app negotiates ws:// automatically - there is no manual TLS toggle to flip.

The command sets VITE_WEB_BASE=/ so the bundle is served at the site root rather than the /app subpath used for the hosted HTTPS deployment, then runs vite preview with --host (also reachable on the LAN, though a LAN IP origin is not a secure context and won't register the service worker).

Build

bash
cd crates/hive-app
pnpm build:web        # vue-tsc + vite build --mode web

Output lands in crates/hive-app/dist-web/ with the /app/ base path, including index.html, hashed assets, manifest.webmanifest, sw.js, and the icon set. Preview it locally with pnpm preview:web.

Deploy

The build and deploy are wired into scripts/hive.ps1. The web client is published into the distribution folder (served at the HIVE_DISTRIBUTION_URL, default https://hive.vazac.dev) under /app:

bash
# Build + publish in one step
pwsh ./scripts/hive.ps1 deploy web

# Build only (no publish) -> crates/hive-app/dist-web
pwsh ./scripts/hive.ps1 build web

deploy web replaces the previous bundle wholesale so stale hashed assets and old service-worker files don't linger. A regular deploy distribution also syncs an already-built dist-web/ (if present) under /app, but never rebuilds it - use deploy web to build and publish together.

Because it is a single-page app, the host must fall back to /app/index.html for unknown paths under /app so a deep-link refresh doesn't 404. For nginx:

nginx
location /app/ {
    try_files $uri $uri/ /app/index.html;
}

iOS install

On iPhone/iPad, open https://hive.vazac.dev/app in Safari, then Share -> Add to Home Screen. The app launches fullscreen (no Safari chrome) using the apple-mobile-web-app-capable meta tag and the apple-touch icon. Make sure the nodes you connect to serve wss:// (see above), or connections will be blocked.

Hive - remote AI coding agents over WebSocket.