Documentation

Detailed references

v2 · Stable

React frontends

A game ships two source files:

Path Holds
/src/game.ts Required. gameConfig plus the game-logic hooks. The server runs these authoritatively.
/src/frontend.tsx Required. The React client. Its single default export is a React component that renders the whole board.

/src/game.ts holds the rules : pieces, spaces, applyActions, getAvailableActions, scoring, turn passing. /src/frontend.tsx holds everything the player sees. Both files exist from the moment the game is created; the starting template includes them.

The split is strict, and it is what keeps a game honest: the server is the sole authority over shared state. Your frontend never re-implements rules. It reads state and sends clicks; the server decides what those clicks mean, by running the same /src/game.ts you wrote.

The component contract

import { useMatch, useGameState, useAvailableActions } from "boardweaver/react";

export default function App() {
  const match = useMatch();
  const game = useGameState();
  const available = useAvailableActions();

  return (
    <div>
      {game.spaces("plot").map((space) => (
        <button
          key={space.spaceId}
          onClick={() => match.click({ type: "Click", spaceId: space.spaceId })}
        >
          {space.spaceId}
        </button>
      ))}
    </div>
  );
}

Hard requirements:

  • A default export. No default export is a build error, caught at commit time.
  • No props. The platform mounts your component; nothing passes it anything.
  • Never mount anything yourself. The runtime owns the React root. Do not import react-dom, do not call createRoot.
  • Do not import react for rendering primitives you don't need. react is available for hooks and types, but React itself is provided by the platform and version-locked : your bundle must not ship its own copy.

What is deliberately unavailable

Your frontend runs in a sandboxed frame with no network access, on an opaque origin, under a strict Content Security Policy. These are compile errors, and they would fail at runtime even if they weren't:

  • fetch, XMLHttpRequest, WebSocket, EventSource : the frame cannot talk to any server. All data arrives through the hooks.
  • localStorage, sessionStorage, indexedDB, document.cookie : an opaque origin has no persistent storage.
  • window, document, direct DOM access : use React. For frame size use useViewport(), which is authoritative; there is no window worth measuring.

console and timers (setTimeout, setInterval, requestAnimationFrame) work normally.

This is not a restriction to work around : it is the containment boundary that lets untrusted game code run safely in a player's browser. If you find yourself needing one of these, the answer is almost always that the data belongs in game state, where the server owns it.

Assets and presentation

  • useImage(GameImage.Something) resolves a game image to a URL for <img src>. Image keys come from the same per-game GameImage enum exported by "boardweaver" that /src/game.ts uses.
  • useGameTheme() returns the game's validated /theme.json document; pair it with useColorMode() to pick the light or dark side of each color pair.
  • useViewport() gives the frame's current size in CSS pixels and re-renders on change.

On a phone

Below 768 px wide, the frame fills the whole screen. Boardweaver shows no top bar, sidebar or action bar during a match. The one control it keeps is the Boardweaver mark: a 44 px round button, 12 px in from a corner of the frame, floating over your game. Players tap it to switch matches and drag it to any of the four corners. It starts in the top-left corner.

Keep controls a player must reach (buttons, a hand of cards) out of a 56 px square in each corner, or make sure the mark covering one of them never blocks a move.

The phone drawer brings match switching and sound controls into reach without leaving the game.
The phone drawer brings match switching and sound controls into reach without leaving the game. Example data from the local development UI. Select the image to enlarge.

Available modules

Only these imports resolve. Bare imports of anything else fail the build.

Module What it is
boardweaver The same runtime the backend uses: GameImage, GameFont, defs, types.
boardweaver/react The match API : every hook in this doc. See react-match-api.
boardweaver/immer Version-locked Immer (produce, produceWithPatches, applyPatches, Patch) for client state. See react-client-state.
boardweaver/dnd Drag and drop primitives.
react, react/jsx-runtime Hooks and types. Provided by the platform.
/src/** Your own tree, by relative or absolute path.

Bundle size

The client bundle is /src/frontend.tsx and all of /src/game.ts, built together into one file that ships to every player on every match load. validate_code rejects it over 1024 KB, measured after minification.

game.ts is in there because the client runs your rules locally to predict the result of a click before the server answers. It is re-exported wholesale, so nothing in it is dropped for going unused by the UI: a large rules engine spends the budget whether or not your frontend touches it. There is currently no way to keep the rules server-only.

The bundle is minified, and comments never survive the build, so neither is worth trimming for size. What counts is executable code. Two things do not count at all:

  • Platform modules. react, boardweaver/react, boardweaver/dnd, boardweaver/immer, boardweaver/ui and boardweaver/motion are runtime externals resolved to the frame, not copies in your bundle.
  • Game images and fonts. Uploaded assets are fetched by URL. Inlining an image as a base64 string literal is the one case where an asset does land in the bundle, and it is the fastest way to blow the limit.

Next

  • react-match-api : every hook, the click lifecycle, and what re-renders when.
  • react-client-state : selection, tentative moves, and undo.
  • build-react-game-instructions : the end-to-end build workflow.
  • v2-game-source : what /src/game.ts must export, and the config object.
  • v2-hooks : every hook's contract, and the action union.
  • v2-entities : PieceDef / SpaceDef and the three state buckets.
  • v2-game-state : the GameState read/write API, shared with useGameState().

Need a hand with this?

Join our Discord