Build & Deploy
The full dev loop for a web game: build it, pack it, and run it on a device. The flow is cross-platform (macOS, Linux, Windows) and works the same for a person at a keyboard or an automated agent.
Prerequisites
- A Board device on the MP.1.9.x OS family or newer. The device must expose the web-app install and logs capabilities; confirm with
board-connect capabilities(see Confirm device compatibility). - Node 18 or newer for the build toolchain.
- The
web-packandboard-connecttools available on your machine. - A Piece Set Model (
model.tflite) from the developer portal — every web app bundles one (see Every app needs a model).
1. Build the web app
Projects scaffolded with
npm create @board.fun/gamecome with all of this pre-configured — relative base path, pack script, and model placeholder — so you can skip straight to deploying.
Produce a static build with relative asset paths so it loads correctly when served from the device. With Vite, set the base path to './':
// vite.config.js
export default {
base: "./",
};
vite build
This writes your app to dist/. Any bundler works as long as it emits a static build with relative paths.
Relative paths are not optional. Most bundlers default to site-root URLs (
src="/assets/index.js"), which assume the app is served from the root of a website. On a Board the app is served from a folder, so those URLs resolve to nothing — the result is a white screen with no console output, because the app’s JavaScript never loads. Checkdist/index.html: everysrc/hrefshould start with./, not/. (web-packrefuses to pack a build with root-absolute references; in Vite the fix isbase: "./", in webpackoutput.publicPath: "./".)
2. Pack the app
Use web-pack to turn the build into an installable Board web app:
web-pack dist --package-id fun.board.mygame --name "My Game"
web-pack writes a harness-config v1 manifest and a single flat <appId>.webapp.zip. It uses a pure-JS zip implementation, so it produces identical archives on Windows, macOS, and Linux.
This expects model.tflite in dist/ — every web app bundles a Piece Set Model (see Every app needs a model); pass --model <path> if it lives elsewhere.
App identity: packageId, appId, and board.config.json
The manifest carries two identifiers, mirroring an APK:
packageIdis a reverse-domain key (e.g.fun.board.mygame) and the app’s canonical identity. You supply it via--package-id, or viapackageIdin an existingharness-config.jsonorboard.config.json. It is required, and it is never derived from the filesystem path, so building from a different checkout location produces the same bundle identity.appIdis a canonical UUID the device scopes saves and profiles by. The first time you runweb-pack, it mints a random UUID and persists it toboard.config.jsonin your project. The appId is deliberately not derived from the package id: the device namespaces each app’s save directory by appId, so a predictable id would let anyone compute (and overwrite) another app’s saves. Pass--app-id <uuid>only to adopt a pre-existing id.
Commit board.config.json to source control. The appId ties your app to its saved games on the device. If the appId changes, the device treats the app as a different app and the old saves are no longer associated with it. Keeping board.config.json in the repo means rebuilds reuse the same appId and saves survive.
What web-pack validates
web-pack runs the checks the device’s install gate enforces, so a bad bundle fails on your machine instead of after an upload:
harness-config.jsonpresent (generated byweb-pack).schemaVersionequals1.packageIdis a valid reverse-domain id.appIdis a canonical UUID (random, minted once and persisted toboard.config.json).nameis present (1-64 chars).sdkVersionis semver (stamped from the installed@board.fun/web-sdk).- The
entryfile (defaultindex.html) exists in the bundle and is.html/.htm. - The entry HTML has no root-absolute asset URLs (
src="/assets/..."): the device serves the app from a folder, not a site root. Use a relative base path (Vite:base: "./"). modelis present: a bundle-relative file path that exists (its sha256 is recorded). Every Board web app bundles a touch model.entry/model/iconresolve inside the bundle root (no../absolute-path escapes).- Some
.js/.htmlreferences a Board SDK global (window.BoardSDK/window.Harness/window.__board/window.boardTouch), i.e. the bundle was built with@board.fun/web-sdk.
Run web-pack -h for the full list of options.
Every app needs a model
Every Board web app bundles a Piece Set Model. The on-device touch detector is what delivers contacts — finger and Piece — to Board.input, and it only runs when the bundle ships a model. A bundle without one installs and launches, but the device disables Board input entirely: subscribe connects and then no frames ever arrive. web-pack refuses to pack a bundle with no model for exactly this reason.
The model is obtained out of band:
- Download
model.tflitefor your game’s Piece Set from the developer portal. - Keep it in your build output — e.g.
public/model.tflitein a Vite project, so the bundler copies it intodist/— andweb-packpicks it up automatically. Or keep it elsewhere and pass it explicitly:
web-pack dist --package-id fun.board.mygame --name "My Game" --model ./model.tflite
web-pack records the model’s sha256 in the manifest. The model is never bundled automatically by the tooling and never downloaded at runtime.
If your game doesn’t use Pieces, bundle a model anyway: finger input runs through the same detector, so any Piece Set Model enables it — the Glyph recognition simply goes unused. See Input & Pieces.
Confirm device compatibility
Check that the target device exposes the capabilities this workflow needs:
board-connect capabilities
Look for the web-app install and logs capabilities. The device must be on the MP.1.9.x family or newer for these to be present.
3. Deploy to a device
Pair with the device once so later commands need no address (you tap Approve on the device):
board-connect pair <host>
Install the packed app with board-connect and launch it:
board-connect install <appId>.webapp.zip --launch
Watch its output while it runs (add --follow to stream live until Ctrl-C):
board-connect logs <appId>
Grab a screenshot of the current screen:
board-connect screenshot --out shot.png
board-connect is the supported cross-platform path and is the right tool for an automated agent driving the dev loop. Download it from the developer portal at dev.board.fun.
Or use the Board Connect web UI
The same install works without the CLI: open the Board’s address in a browser (shown on the device under Settings > System, along with the pairing screen), pair, and drop the .webapp.zip onto the Apps tab.
The dev loop
Change code, vite build, web-pack, board-connect install, watch board-connect logs, repeat.
Device note: first install reports host unavailable
On a cold device, the in-device browser host can sit in a restricted standby bucket, and the first install may report that the host is unavailable. If that happens, foreground the Board Browser on the device once to wake it, then retry the install. Subsequent installs work without this step.
Troubleshooting
White screen after launch, and board-connect logs shows nothing. Your build references its assets with root-absolute URLs, so the app’s JavaScript never loads (and code that never runs logs nothing). Open dist/index.html: if any src/href starts with /, set your bundler’s base path to ./ (Vite: base: "./"; webpack: output.publicPath: "./") and rebuild. Current web-pack versions catch this at pack time.
No frames from Board.input.subscribe (fingers or Pieces). The installed bundle has no Piece Set Model, so the device launched it with Board input disabled. See Every app needs a model, then re-pack and re-install.
board-connect logs reports an error or shows nothing. The id is the app’s appId UUID (in board.config.json, printed by web-pack, and listed by board-connect apps) — not your package id. logs takes the id as its only argument; the target Board comes from pairing (-b <ip> to override).
Next
Once your game runs on a device, revisit the Guides to wire up sessions, saves, and the pause overlay.