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.

Sign-in requirement

The web client requires signing in with a Hive account that holds an active license - there is no per-device activation on the web (that exists only in the desktop and mobile apps). Create an account and start the free 14-day trial at hive.vazac.dev/account. See Accounts & Licensing for the full picture: trials, license keys, device management, and offline behavior.

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/clusto-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.

Channels and updates

The web client is published on two channels, each a separate installable PWA with its own service-worker scope:

  • Stable - https://hive.vazac.dev/app (base /app/). The channel most people install.
  • Unstable - https://hive.vazac.dev/app-unstable (base /app-unstable/). Carries the newest build for testing.

During the Clusto A0 transition, the same builds are also reachable through https://clusto.app/app and https://clusto.app/app-unstable. The Clusto host is a TLS alias only. hive.vazac.dev remains canonical during A0, and the alias must not receive a different service worker or channel build.

Because the two live under different paths, a device is pinned to whichever URL it installed - the stable install never silently jumps to unstable code.

When a newer build lands on the channel a device is on, the running app shows an Update available banner with Reload and Later buttons; Reload activates the waiting service worker and reloads onto the new build. An open PWA re-checks for a new service worker about once an hour and again when it returns to the foreground, so a shipped update surfaces promptly instead of only on the browser's own (roughly daily) check.

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.

Licensed accounts have a third option that skips TLS setup entirely: Hive Tunnel. Nodes with the tunnel enabled are reachable from the web client through a cloud relay on the same HTTPS origin, no wss:// or certificate on the node required.

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/clusto.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/clusto-app
pnpm build:web        # vue-tsc + vite build --mode web

Output lands in crates/clusto-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/clusto.ps1.

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

The web PWA is served from the cloud web origin, hive.vazac.dev. Both channels (/app and /app-unstable) and the docs site ship together as part of the cloud deploy, which builds each channel with its own base path and rsyncs the result to the origin:

bash
# Build + publish the cloud origin: landing, docs, accounts, and both PWA channels
pwsh ./scripts/clusto.ps1 deploy cloud

dl.vazac.dev serves only binaries, release manifests, and install.sh - no web assets.

Redeploys overlay the content-hashed assets/ chunks rather than wiping them, so a client still running the previous index.html can keep lazy-importing its old-hash chunks after a ship; orphaned chunks past a grace window are pruned so the folder stays bounded.

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

nginx
location /app/ {
    try_files $uri $uri/ /app/index.html;
}
location /app-unstable/ {
    try_files $uri $uri/ /app-unstable/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.