AI Assistant

This page does two things. First, it tells you how to set up Claude, Cursor, GitHub Copilot, or any other AI coding assistant so it can write correct, idiomatic Board Web SDK code. Second, the Quick Reference section below IS the payload you can paste directly into your tool of choice as a system prompt, a rules file, or a top-of-file comment block. Copy-paste-and-go.

Note: Everything here is optional. The SDK works the same with or without AI assistance. If you would rather hand-roll, the API Reference and the FAQ have every signature and gotcha you need.

Setup

Claude

Claude reads any CLAUDE.md files in or above the directory it is working in. Drop the Quick Reference below into a CLAUDE.md at your project root and Claude will follow the conventions for free.

Cursor

Cursor reads a rules file at your project root and injects it into every chat. Copy the entire Quick Reference section below into that file, commit it to source control, and the whole team gets the same context.

GitHub Copilot and others

Copilot does not have a project-level rules file. The next-best thing is a top-of-file comment block in your main module that contains the Quick Reference below; Copilot reads surrounding file context aggressively and picks up the conventions. For ChatGPT, Aider, or any tool with a system-prompt slot, paste the Quick Reference in directly.


Quick Reference

(This is the section to copy into your AI tool. Everything below this line is written as a self-contained briefing for an AI assistant.)

You are writing TypeScript against the Board Web SDK (@board.fun/web-sdk). Board is a 23.8” 1080p landscape touch console; its touch sensor detects both fingers and physical Pieces (game tokens with conductive Glyph patterns on their bases). The SDK is ESM-only and runs your web app inside Board’s built-in browser.

Basics

  • Import the single frozen Board object: import { Board, BoardContactType } from "@board.fun/web-sdk";. Every feature hangs off it across six domains: Board.input, Board.session, Board.save, Board.avatar, Board.pause, Board.application.
  • Always gate device calls on Board.isOnDevice. In a desktop browser it is false and service-backed calls do NOT no-op: sync calls (Board.session.*, Board.pause.*, Board.application.*, the save getters) throw, and async calls (Board.save.*, Board.avatar.*, Board.session.present*) reject because the native bridge is absent. The one exception is Board.input.getContacts(), which safely returns []. Gate sync calls behind an if (Board.isOnDevice) check and wrap async calls in try/catch so the same build stays runnable without hardware.
  • Do not gate features on Board.sdkVersion. It is informational only. There is no bridge or OS version exposed to games; new OS capabilities degrade gracefully via in-bridge capability checks. The runtime gate is Board.session.areServicesReady().
  • Two async styles: Promises for request/response calls, and callbacks/subscriptions for the touch stream and pause results.

Touch input

Subscribe to Board.input; each callback receives a full per-frame snapshot of contacts. Filter Pieces by glyphId, not by the contact type.

function onContacts(contacts: BoardContact[]) {
  for (const c of contacts) {
    if (c.type === BoardContactType.Glyph) {
      handlePiece(c.glyphId, c.x, c.y, c.orientation);
    }
  }
}

if (Board.isOnDevice) {
  Board.input.subscribe(onContacts);
}

Each contact: contactId, type (BoardContactType.Finger / Glyph / Blob), glyphId, x, y (device pixels, origin top-left, Y down), orientation, phase. There are no discrete down/up events; keep your own previous-frame map keyed by contactId and diff it for edges. All contacts — finger and Piece — come from the on-device detector, which only runs when the bundle ships a Piece Set Model (recorded at pack time): a bundle with no model gets no input frames at all.

Players and sessions

The roster is OS-owned; the game never silently adds or removes players. Read with getPlayers() / getPlayerCount() / getActiveProfile(). Change it through the OS selector (presentAddPlayer(aiTypeIndices?) / presentReplacePlayer(sessionId, aiTypeIndices?), both resolve Promise<boolean>, true = added/replaced, false = dismissed) or resetPlayers(). Declare AI types with setAIPlayerTypes([{ name, description }]).

const added = await Board.session.presentAddPlayer();
if (added) refreshRoster();

Save games

Create, load, list, and update, all Promise-based: create, load, list, update. There is no direct delete. A game removes its own involvement with removePlayersFromSave (the current game’s players) or removeActiveProfileFromSave (only the active profile); the system deletes the save once no players remain. Plus loadCoverImage, getAppStorageInfo(), and getUniquePlayers(saves).

const meta = await Board.save.create("Turn 12", payload /* Uint8Array */, playedTimeMs, gameVersion);
const saves = await Board.save.list();
await Board.save.removePlayersFromSave(meta.id);
// or, to remove only the active profile:
await Board.save.removeActiveProfileFromSave(meta.id);

Save metadata: id, description, createdAt, updatedAt, playedTime, fileSize, gameVersion, playerCount, players[] (each playerId, name, avatarId, type, aiTypeIndex), hasCoverImage, payloadChecksum, coverImageChecksum.

Avatars

Board.avatar.loadPNG(avatarId) resolves to a cached PNG data URI; getDefault() is avatar 0; forPlayer(player) is a shortcut. Assign the data URI straight to an <img> element’s src.

Pause overlay

The OS owns the menu button and UI; the game supplies context and reads results. setContext(context), updateContext(partial), clearContext(). Subscribe with onResult(callback) (preferred); a legacy pollResult() exists.

Board.pause.setContext({
  offerSaveOption: true, // adds the system "Save & Quit" option
  customButtons: [{ id: "restart", title: "Restart", icon: "circulararrow" }],
});
Board.pause.onResult((result) => {
  if (result.action === "quit" || result.action === "save_and_quit") Board.application.quit();
  if (result.action === "custom_button" && result.customButtonId === "restart") restartGame();
});

Application lifecycle

Board.application.quit() closes the web app and returns to the launcher. showProfileSwitcher() / hideProfileSwitcher() drive the OS profile switcher.

Build and deploy

For a new project, scaffold with npm create @board.fun/game my-game (add -- --template showcase for a reference app exercising every SDK domain) — it pre-wires the base path, pack scripts, and model placeholder. Otherwise: build a static app with relative paths (base: "./" in Vite). Pack with web-pack dist --package-id fun.board.mygame --name "My Game", which writes a flat <appId>.webapp.zip and mints a random-UUID appId persisted to board.config.json (commit that file so saves survive rebuilds). Every bundle ships a Piece Set Model (required — finger input needs it too): download it out of band from the developer portal, keep it in your build output (e.g. public/model.tflite) so web-pack picks it up, or pass --model <path>; the tooling never vendors or downloads it at runtime. Pair once with board-connect pair <ip> (tap Approve on the device), which saves that Board as the default so later commands need no address. Then deploy with board-connect install <appId>.webapp.zip --launch, watch board-connect logs <appId> --follow, and confirm device support with board-connect capabilities (MP.1.9.x family or newer). If the first install reports the host unavailable, foreground the Board Browser once and retry.


Important notes

  • Gate on Board.isOnDevice, not on Board.sdkVersion. The runtime capability gate is Board.session.areServicesReady().
  • Y-down coordinates, no flip. Contacts are device pixels, origin top-left.
  • Identify Pieces by glyphId, not by the contact type.
  • There is no direct save delete. A game removes its own players with removePlayersFromSave or removeActiveProfileFromSave. The system deletes the save automatically once no players remain associated with it. A game cannot directly delete a shared save, which protects other players’ progress. This matches the Unity SDK.
  • Commit board.config.json so the appId (and therefore saved games) survives rebuilds.

For more depth see the API Reference and the Guides. File feedback at hello@board.fun.