diff --git a/.github/workflows/verify.yml b/.github/workflows/verify.yml
deleted file mode 100644
index f96182f..0000000
--- a/.github/workflows/verify.yml
+++ /dev/null
@@ -1,139 +0,0 @@
-# The ShimmerCapture verification pass, on every pull request and on main.
-#
-# `common/dev/verify.mjs` drives the real page in a real browser over CDP
-# against the ?mock=1 transport: ~290 checks over the connect handshake, the
-# configuration form, the sensor rules, the plot, the CSV recorder, the SD
-# browser, the self-test and the clock. It has existed for a while and nothing
-# ran it, so its failures were invisible unless somebody ran it by hand and
-# read the output — which is how a block of eleven broken checks survived, and
-# how a regression could have hidden among them.
-#
-# It gates on CHANGE, not on the total: `common/dev/verify-known-failures.json`
-# lists what is known broken and why, a failure outside that list fails the
-# build, and a listed check that starts passing ALSO fails the build so the
-# list shrinks rather than rots. See the tail of verify.mjs.
-name: Verify ShimmerCapture
-
-# `pull_request` covers the work; `push` is scoped to main and is not
-# redundant with it. A pull_request run tests the PREDICTED merge of the branch
-# into main, not main as it ends up: two pull requests each green against an
-# older main, merged in sequence, can produce a main that no run ever tested.
-# There is no branch protection here forcing a branch to be up to date first,
-# so the run on main after a merge is the only thing that says main works.
-#
-# Together they are also exactly one run per push, where `push:` unscoped
-# alongside `pull_request` gave two of everything - four, once the CRC matrix
-# went in.
-#
-# A branch with no pull request open gets no run, which is why
-# workflow_dispatch is here: it can be started by hand on any ref.
-on:
- pull_request:
- push:
- branches:
- - main
- workflow_dispatch:
-
-# One run per ref. A second push a minute later cancels the first, because only
-# the newest commit's result is interesting. Not on main, where each run is the
-# record of a commit that is already merged and cancelling one would leave a
-# gap in exactly the history this job exists to keep.
-concurrency:
- group: ${{ github.workflow }}-${{ github.ref }}
- cancel-in-progress: ${{ github.ref != 'refs/heads/main' }}
-
-jobs:
- verify:
- # Both link-CRC settings, because the difference between them is diagnostic
- # and nothing else in the pass reports it. A CRC changes more than a
- # trailer: `_reframing` turns on, so every inbound chunk goes through the
- # SDK's framer even over BLE, and a whole class of defect only exists on
- # that path: eighteen checks were failing on it at once, from three
- # separate causes, and the CRC-off run coming back clean is what said they
- # were defects of that path rather than flaky tests. A failure in one
- # variant and not the other says where to look.
- #
- # The CRC-off run does not block: the shape a user is actually on is the
- # default, two bytes, and a red mark on the variant would stop a merge for
- # something the product does not do. It is here to be read.
- name: verify (${{ matrix.label }})
- runs-on: ubuntu-latest
- timeout-minutes: 25
- continue-on-error: ${{ matrix.crc == '0' }}
- strategy:
- # Independent runs, so one failing must not cancel the other -- the
- # comparison between them is the whole point.
- fail-fast: false
- matrix:
- include:
- - crc: "2"
- label: link CRC on
- - crc: "0"
- label: link CRC off
- steps:
- - name: Checkout repository
- uses: actions/checkout@v4
-
- - name: Setup Node.js
- uses: actions/setup-node@v4
- with:
- node-version: "24"
-
- # Pinned rather than relying on whatever Chrome the runner image carries,
- # so a runner-image change cannot move the browser under the pass.
- - name: Setup Chrome
- id: chrome
- uses: browser-actions/setup-chrome@v1
- with:
- chrome-version: stable
-
- # One step for the server, the browser and the pass. Backgrounded
- # processes are not guaranteed to outlive the step that started them, and
- # a pass that cannot say WHY the browser did not come up is a pass
- # nobody can fix from the log — so both get a log file, and both logs are
- # printed when something goes wrong.
- - name: Run the verification pass
- env:
- # Chrome's sandbox does not come up on the runners, and this is a
- # throwaway container loading only localhost.
- CHROME_FLAGS: >-
- --headless=new --no-sandbox --disable-gpu --disable-dev-shm-usage
- --no-first-run --no-default-browser-check
- run: |
- set -uo pipefail
- CHROME='${{ steps.chrome.outputs.chrome-path }}'
- echo "chrome: $CHROME"
- "$CHROME" --version
-
- # Serves the REPO ROOT, not the demo folder: the cross-demo checks
- # walk up from the demo to the root to police which files may import
- # the extension's private SDK copy.
- npx --yes http-server . -p 8129 -c-1 --silent > "$RUNNER_TEMP/http.log" 2>&1 &
- "$CHROME" $CHROME_FLAGS --remote-debugging-port=9333 \
- --user-data-dir="$RUNNER_TEMP/chrome-verify" \
- about:blank > "$RUNNER_TEMP/chrome.log" 2>&1 &
-
- up() { curl -fsS -o /dev/null "$1"; }
- for i in $(seq 1 60); do
- if up http://localhost:8129/ShimmerCapture/ && up http://127.0.0.1:9333/json/version; then
- ready=1; break
- fi
- sleep 1
- done
- if [ "${ready:-0}" != "1" ]; then
- echo "::error::the static server or Chrome never came up"
- echo "--- http-server ---"; cat "$RUNNER_TEMP/http.log" || true
- echo "--- chrome ---"; cat "$RUNNER_TEMP/chrome.log" || true
- exit 1
- fi
-
- # Strict here and only here: CI is the environment the baseline
- # describes, so a listed check that starts passing has to fail the
- # build. Elsewhere it is a note, or nobody could run the pass on a
- # laptop without editing the baseline first.
- VERIFY_CRC='${{ matrix.crc }}' VERIFY_STRICT_BASELINE=1 node common/dev/verify.mjs
- status=$?
- if [ "$status" != "0" ]; then
- echo "--- chrome ---"; tail -40 "$RUNNER_TEMP/chrome.log" || true
- fi
- exit $status
diff --git a/.prettierignore b/.prettierignore
index 1a28ce8..8761bd8 100644
--- a/.prettierignore
+++ b/.prettierignore
@@ -1,12 +1,10 @@
# Vendored third-party bundles. They are checked in byte-identical to the
-# upstream build (see common/vendor/README.md), and prettier un-minifies a
-# minified file -- chart.umd.min.js grows from 204 KB to 342 KB, and the diff
-# is the whole library. Nothing here is ours to format.
+# upstream build, and prettier un-minifies a minified file -- the diff is the
+# whole library. Nothing here is ours to format.
#
# The HTML formatting workflow (.github/workflows/html-format.yml) only ever
# touches **/*.html, so this file changes nothing for it; it exists to protect
# these bundles from anyone running prettier over the repo by hand.
-common/vendor/chart.umd.min.js
shimmer-extension/chart.min.js
shimmer-extension/jszip.min.js
shimmer-extension/vendor/
diff --git a/AGENTS.md b/AGENTS.md
index 648ac1f..c2dcfee 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -5,26 +5,38 @@ self-contained directory of static files. Published to GitHub Pages at
`shimmerresearch.github.io/webBLEDemos/`.
## The vendor path that matters
-**`vendor/` at the repo root is the one almost everything imports** — 18 of the 19 pages plus
-`common/`, ShimmerCapture included. There is no `Verisense/vendor`; a sync script once targeted that
-path, threw, and silently left the real `vendor/` un-updated. If a demo is running stale SDK
-behaviour, check which vendor directory was actually written.
+
+**`vendor/` at the repo root is the one almost every page imports.** There is no
+`Verisense/vendor`; a sync script once targeted that path, threw, and silently left the real
+`vendor/` un-updated. If a demo is running stale SDK behaviour, check which vendor directory was
+actually written.
+
+The same bundle is vendored again in `verisense-device-console` and in `shimmer-capture-web`.
+`C:\dev\web\sync-all-vendors.ps1` writes all three; prefer it over this repo's script alone.
Update with the scripts, never by hand — they stamp `sdk-source.json`:
+
```
./sync-local-sdk.ps1 # sync only
./update-local-sdk.ps1 # build the SDK first, then sync
```
## Layout
+
Shimmer3R demos: `break-gyro/`, `break-emg/`, `punch-highG/`, `brick/`, `rythmgame-emggyro/`,
-`video-ppg/`, `spell-gyro/`, `ShimmerCapture/`, `consensys-export/`.
-Verisense demo: `Verisense/`. Shared code: `common/`.
+`video-ppg/`, `spell-gyro/`, `consensys-export/`.
+Verisense demo: `Verisense/`.
`shimmer-extension/` is a Chrome extension source tree — loaded unpacked, not deployed with the pages.
+`ShimmerCapture/` is a redirect stub only — the page moved to
+[shimmer-capture-web](https://github.com/ShimmerResearch/shimmer-capture-web) and took `common/`,
+the shared UI library nothing else here imported, with it. Leave the stub in place; published
+links point at it.
## Demo requirements
+
Shimmer3R demos need firmware ≥ v1.0.22 and Chrome/Edge — Web Bluetooth for BLE, Web Serial for
Classic Bluetooth and USB. Keep that stated on any new demo page.
## CI
-`verify.yml` and `html-format.yml`.
+
+`html-format.yml` only. `verify.yml` went to `shimmer-capture-web` with the page it tested.
diff --git a/README.md b/README.md
index 67997d6..4069872 100644
--- a/README.md
+++ b/README.md
@@ -18,9 +18,9 @@ brick/ │
rythmgame-emggyro/ │ Shimmer3R demos
video-ppg/ │
spell-gyro/ │
-ShimmerCapture/ │
consensys-export/ ┘
Verisense/ ← Verisense demo
+ShimmerCapture/ ← redirect stub only; the page moved to shimmer-capture-web
shimmer-extension/ ← Shimmer3R/Verisense Chrome extension (source; load unpacked in Chrome)
sdk-source.json ← Single source-of-truth for SDK source mode/version
update-local-sdk.ps1 ← Build + sync local SDK artifacts
@@ -28,9 +28,14 @@ sync-local-sdk.ps1 ← Sync-only local SDK artifacts
update-local-sdk.cmd ← Windows CMD launcher for update script
```
-The full Verisense control console now lives in a dedicated repository:
+Two of these grew past being demos and now live in dedicated repositories:
- [ShimmerResearch/verisense-device-console](https://github.com/ShimmerResearch/verisense-device-console)
+ — the full Verisense control console.
+- [ShimmerResearch/shimmer-capture-web](https://github.com/ShimmerResearch/shimmer-capture-web)
+ — Shimmer Capture, which took `common/` (the shared UI library nothing else
+ here imported) and the `verify.yml` pass with it. `ShimmerCapture/` here is a
+ redirect stub; leave it in place, published links point at it.
---
@@ -40,26 +45,25 @@ The full Verisense control console now lives in a dedicated repository:
**Requirements:** Shimmer3R device, firmware ≥ v1.0.22, Chrome/Edge (Web Bluetooth for BLE; Web Serial for Classic Bluetooth/USB)
-| Demo | Link |
-| -------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------- |
-| Gyro breakout game | [break-gyro](https://shimmerresearch.github.io/webBLEDemos/break-gyro/) |
-| EMG breakout game | [break-emg](https://shimmerresearch.github.io/webBLEDemos/break-emg/) |
-| 200 G accel punch detector | [punch-highG](https://shimmerresearch.github.io/webBLEDemos/punch-highG/) |
-| EMG + Gyro rhythm game | [rythmgame-emggyro](https://shimmerresearch.github.io/webBLEDemos/rythmgame-emggyro/) |
-| PPG heart-rate visualiser | [video-ppg](https://shimmerresearch.github.io/webBLEDemos/video-ppg/) |
-| Two-device gyro brick game | [brick](https://shimmerresearch.github.io/webBLEDemos/brick/) |
-| Spell caster (gyro gestures) | [spell-gyro](https://shimmerresearch.github.io/webBLEDemos/spell-gyro/) |
-| Configure, stream, plot, record, browse the SD card, set device names, run the self-test and check clock drift | [ShimmerCapture](https://shimmerresearch.github.io/webBLEDemos/ShimmerCapture/) |
-| Consensys trial export + Bluetooth RTC set | [consensys-export](https://shimmerresearch.github.io/webBLEDemos/consensys-export/) |
+| Demo | Link |
+| ------------------------------------------ | ------------------------------------------------------------------------------------- |
+| Gyro breakout game | [break-gyro](https://shimmerresearch.github.io/webBLEDemos/break-gyro/) |
+| EMG breakout game | [break-emg](https://shimmerresearch.github.io/webBLEDemos/break-emg/) |
+| 200 G accel punch detector | [punch-highG](https://shimmerresearch.github.io/webBLEDemos/punch-highG/) |
+| EMG + Gyro rhythm game | [rythmgame-emggyro](https://shimmerresearch.github.io/webBLEDemos/rythmgame-emggyro/) |
+| PPG heart-rate visualiser | [video-ppg](https://shimmerresearch.github.io/webBLEDemos/video-ppg/) |
+| Two-device gyro brick game | [brick](https://shimmerresearch.github.io/webBLEDemos/brick/) |
+| Spell caster (gyro gestures) | [spell-gyro](https://shimmerresearch.github.io/webBLEDemos/spell-gyro/) |
+| Consensys trial export + Bluetooth RTC set | [consensys-export](https://shimmerresearch.github.io/webBLEDemos/consensys-export/) |
**Consensys Export** packages a logged Shimmer3/Shimmer3R trial into the Consensys import folder structure, zips it, and shares it. It also sets the device real-time clock over Bluetooth. Best on a Chromium browser (Chrome/Edge); on iPhone/iPad use the [Bluefy](https://apps.apple.com/app/bluefy-web-ble-browser/id1492822055) app for the Bluetooth RTC feature.
-**Shimmer Capture** is a worked example of driving a single Shimmer3R from a browser: connect over **BLE**, **Classic Bluetooth** (a paired COM port, via Web Serial) or **USB-C**, then configure it, stream from it, plot it and record a CSV. The configuration editor is generated from the SDK's description of the InfoMem, so it covers the whole LogAndStream option set — sampling rate, every sensor's range and rate, GSR, expansion power, the SD-logging and trial settings, the sync settings — and it edits the 384-byte image in place, so the bytes no field on the page models survive a read, an edit and a write untouched. The Calibration group in that form shows each 21-byte block as the offset, sensitivity and alignment grids the Calibration tab uses, with the range the image is configured for beside them. There is a hex view of that image with save and load, a calibration-dump reader and writer (read on connect, so the tab is populated before anybody opens it), the decoded device status flags and a real-world-clock set. A **General** tab leads the strip, as in the Verisense device console: the clock, the red LED and the one-shot device commands. Note that **the Shimmer3R's USB-C port speaks the dock protocol, not the Bluetooth one**, so over USB the page configures the sensor but cannot stream from it; it says so rather than offering a button that cannot work. Append `?mock=1` to the URL to drive the whole page against a scripted sensor with no hardware on the desk. Two further tabs cover what used to be separate pages: **SD card** browses the sensor's card and pulls logged sessions off it, in the Consensys import layout — `
+ It now lives at
+ shimmerresearch.github.io/shimmer-capture-web, built from
+ ShimmerResearch/shimmer-capture-web.
+ You should be redirected automatically. If not:
+ Go to Shimmer Capture
-
-
- ` | Put the sensor's clock this many seconds short of the point where the 24-bit stream counter rolls over, so a short stream crosses a wrap. It moves the clock, not the counter, because on a Shimmer3R the two are the same number. |
-| `&debug=1` | Log every command and reply to the browser console. |
-
-While the mock is connected, `mockTransport.writes` in the console is every
-command the page has sent, and `mockTransport.emitDisconnect()` simulates a
-dropped link. `mockTransport.factoryTest` reports how many self-tests have run,
-whether one is still printing and exactly what text it printed;
-`mockTransport.rtc` exposes the sensor's own running clock. The panels
-themselves are `factoryTestPanel` and `rtcDriftPanel`, beside `sdBrowser`,
-`brandEditor` and `calibrationEditor`.
-
-It is opt-in from the URL only, and deliberately so: a page that reached for
-the mock on its own would quietly show fake data to somebody debugging real
-hardware.
-
-## Status
-
-This is an early example. It has been exercised end to end against the mock
-link; the paths that only a real sensor can prove — that a configuration write
-is accepted and applied, that a calibration dump round-trips, that a long
-recording holds up at high rates, that the self-test report arrives whole over
-a real link, that the red LED really lights — want confirming on hardware
-before anyone relies on them for real work. Check a recording before it
-matters.
-
-The calibrated values are the newest part and the least proven. Worth checking
-against Consensys reading the same sensor, in this order:
-
-- **Pressure and temperature.** The whole conversion is host-side and none of it
- has met a real BMP390 or BMP581. A Shimmer3 with a BMP280 is worth its own
- look: its 20-bit registers are reassembled from a 16-bit temperature and a
- 24-bit pressure, and getting that wrong is not subtle.
-- **GSR in µS and kΩ**, and the battery in mV — both against Consensys, and the
- battery against a meter.
-- **ExG in mV** with the test-signal preset, whose amplitude is known.
-- **The time axis.** That a Shimmer3R's packet timestamp really is the low bits
- of its real-world clock is read out of the firmware, not measured; if it is
- wrong the axis will be out by a whole multiple of 512 seconds, which is
- obvious the moment you compare it with the Device panel's clock.
-- **The sensor rules**, by writing an image that breaks one and reading it back:
- the device should correct exactly what the banner predicted.
+A change to `common/` no longer reaches anything in this repository, and a
+change here no longer reaches Shimmer Capture.
diff --git a/ShimmerCapture/index.html b/ShimmerCapture/index.html
index 4515075..3a8d5ce 100644
--- a/ShimmerCapture/index.html
+++ b/ShimmerCapture/index.html
@@ -1,5612 +1,128 @@
-
-
-
-
+
+ Shimmer Capture
- Shimmer Capture has moved
+ .bin. The firmware seeds the record with the factory
- names at first boot and treats it as the only source of truth
- afterwards, so what is shown above is what the sensor really
- announces. Every link reaches the record — BLE, Classic Bluetooth
- and the USB-C/dock connection alike.
-