Skip to content

The app store

The companion app's Store screen holds every app, watch face and plugin it can install, and installs or uninstalls each one on the watch. It starts with every app and face in this repository, bundled into the companion, and takes more from files and from GitHub. This page says how the store works, what CI builds for it, and what a release carries.

In the companion

Open it from the Watch tab (Store) or from the Apps screen (Open the store). It opens with no watch connected, so it can be browsed and filled first; installing waits for a watch. Each entry shows whether it is installed, its size and where it came from.

  • Install sends an app or face to the watch, which checks it and refuses one that would not fit in memory, as installing from the Apps screen does. A plugin installs together with the watch apps it carries, through the companion's Rust core, so plugins install in the Tauri app on a computer and on Android.
  • Uninstall removes the app or face from the watch, with what it saved. For a plugin it removes the plugin's watch apps and the plugin.
  • Remove from the store takes the entry out of the list. A bundled entry stays out until Bring back the bundled entries at the bottom of the screen.
  • Add from files adds .ntapp and .ntplugin files, recognised by their contents.
  • Look on GitHub lists the releases of a repository, BeatLink/BattleTime unless changed, that carry battletime-store.zip or loose .ntapp and .ntplugin files; with a GitHub token in Settings it lists the battletime-store artifacts of Actions runs too. Choosing one downloads it and offers each app and plugin in it, all switched on, to add. The files come down through the app's own native requests, since GitHub's download host refuses a web page's.

An added entry replaces a bundled or earlier one with the same id, so adding a newer release updates the store; installing it again then updates the watch. The store keeps each added file in the app's storage, so installing needs no download. Apps and faces share the watch's ids and plugins have their own, so an app and a plugin may share a name.

The setup screen for a new watch offers only part of the bundle (a face, the launcher and the apps most people keep); the rest waits in the store.

The code is companion/src/services/app-store.ts (entries, keeping them, installing) and companion/src/services/github-store.ts (finding and downloading), with the screen in companion/src/screens/store.tsx.

From the command line

npm run battletime -- store list                          # the bundled apps and faces
npm run battletime -- store releases BeatLink/BattleTime     # releases with store files, numbered
npm run battletime -- store fetch BeatLink/BattleTime 1 apps # download the newest into apps/
npm run battletime -- apps install apps/dice.ntapp        # install a file
npm run battletime -- apps install dice                   # install the bundled dice app

GITHUB_TOKEN makes releases and fetch see Actions artifacts too. The command line keeps no store of its own; plugins it fetches are installed in the app.

Building the store's files

tools/build_store.py, inside nix develop, compiles every battletime/apps/<id>/app.tsx except the template and every battletime/faces/<id>/face.tsx, packs every plugin in companion/plugins/ with scripts/build-plugin.mjs (which needs npm ci in companion/, or pass --no-plugins), and writes battletime/dist/store/:

File What it is
<id>.ntapp Each app and face, named by its id, which must match its folder.
<id>.ntplugin Each plugin, with its watch app inside.
store.json The list below.
battletime-store.zip store.json and every app and plugin file, in one download.
shared.ntlib The shared library, for the companion's bundle only; the firmware carries its own.
SHA256SUMS The checksum of each file.

store.json is format 1:

{
  "format": 1,
  "version": "0.1.0",
  "commit": "71dc807",
  "apps": [
    { "id": "calculator", "name": "Calculator", "kind": "app", "group": "tools",
      "file": "calculator.ntapp", "bytes": 3376, "sha256": "…", "description": "A calculator, …" }
  ],
  "plugins": [
    { "id": "buzz-back", "name": "Buzz back", "version": "1.0.0", "description": "…",
      "watch": ["buzz-back"], "file": "buzz-back.ntplugin", "bytes": 1968, "sha256": "…" }
  ]
}

An app's description is the first paragraph of its page in docs/apps, and a face's comes from the table in faces.md. The companion refuses a store.json of another format. Another repository can offer apps the same way: a release with .ntapp and .ntplugin files is enough, and battletime-store.zip with store.json adds descriptions.

The companion's bundle is the same folder:

cd battletime && nix develop --command python3 -I ../tools/build_store.py
cd ../companion && npm run import-watch-apps ../battletime/dist/store

import-watch-apps marks the apps the setup screen offers; the list is at the top of companion/scripts/import-watch-apps.mjs.

CI and releases

  • .github/workflows/store.yml builds the store whenever an app, face, plugin, the compiler, the runtime, the sdk or their docs change, and keeps it as the battletime-store artifact. On master it also moves the apps-latest pre-release to the commit and replaces its files, so the companion finds the newest apps without a token.
  • .github/workflows/release.yml runs for each v* tag. It builds the watch images with tools/build_images.sh and the store, and publishes both as the tag's release, with one SHA256SUMS over every file. The firmware's own files are described in updates.md.