Skip to main content

Getting started (new engineer)

Everything you need to build, flash, and run each part. Deeper details live in each component guide; this is the "zero to running" path. Every command below is the real one used in this repo, and every version is read from source.

ESP32-S3 nRF52840 Expo SDK 54 · iOS React 19 + Vite 6

Version matrix (source of truth)

Each component is versioned independently. These are read directly from the code — see the Version log for the release history and the exact file each is defined in.

Recorder fw
1.5.13
Pendant fw
1.0.0
Mobile app
0.1.0
Web app
1.5.9
device-api
v15
Expo SDK
54
React Native
0.81.5
React
19.1.0

Toolchain (install once)

ComponentNeedsInstall
Recorderarduino-cli + ESP32 core + lvgl@8.4.0arduino-cli core install esp32:esp32
Pendantarduino-cli + Seeed nRF52 coreboard pkg Seeeduino:nrf52 (see the flash trap in the guide)
Mobile appNode ≥18, Xcode, Expo (SDK 54), a physical iOS devicenpm install in repo root
Web appNode ≥18npm install in react_app_sate-ui_update/
BackendSupabase CLI, Docker (for cf-processor), wranglernpm i -g supabase
Docs siteNode ≥18npm install in docs-site/

Repo layout

PathWhat
SATE_Recorder/Recorder firmware (ESP32-S3) — SATE_Recorder.ino + connectivity.cpp
SATE_Pendant/Pendant firmware (nRF52840) — SATE_Pendant.ino
src/, App.tsxMobile app (React Native / Expo)
react_app_sate-ui_update/Web app (React + Vite)
react_app_sate-ui_update/supabase/functions/Supabase edge functions
cf-processor/Cloudflare Container (Python async AI processor)
cloudflare/Cloudflare Workers port of the backend
hwtest/Hardware-in-the-loop test harness (see Hardware testing)
doc/Original hand-written engineering notes
docs-site/This documentation site (Docusaurus)

Recorder (ESP32-S3)

# Compile (folder name matches the .ino → builds in place, no temp copy)
arduino-cli compile --fqbn "esp32:esp32:esp32s3:FlashSize=16M,PartitionScheme=default_8MB,PSRAM=opi" SATE_Recorder

# Flash the fleet
arduino-cli upload -p <port> --fqbn "esp32:esp32:esp32s3:FlashSize=16M,PartitionScheme=default_8MB,PSRAM=opi" SATE_Recorder

:::danger Two silent bricks — check these first

  • PartitionScheme MUST be default_8MB (dual OTA slots). huge_app silently kills OTA.
  • lv_conf.h LV_TICK_CUSTOM MUST be 1. If 0, the boot spinner freezes at frame 1 while setup() still finishes — looks like a bad flash, isn't. Reinstalling lvgl resets it to 0, so re-check after any lib install lvgl. :::

PSRAM=opi is mandatory. Serial only appears when built CDCOnBoot=cdc,USBMode=hwcdc. Full recipe: Recorder firmware and Operations → Firmware release.

Pendant (nRF52840)

# Build + flash with the SEEED core (NOT Adafruit — see the guide's flash trap)
./SATE_Pendant/flash_xiao.sh SATE_Pendant

Requires the Seeed nRF52 board package. FQBN Seeeduino:nrf52:xiaonRF52840SensePlus. See Pendant firmware.

Mobile app (Expo, iOS)

Plaud is iOS-device-only (no simulator), so this needs a physical device and a custom dev build — Expo Go will not work. Run scripts are in package.json:

npm install # repo root
npm run ios # expo run:ios — build + install the dev client on a device
npm start # expo start --dev-client — Metro bundler for an installed build

Compile-check the native module without a device or signing, and typecheck:

xcodebuild -workspace ios/SATECompanion.xcworkspace -scheme SATECompanion \
-sdk iphoneos -destination 'generic/platform=iOS' CODE_SIGNING_ALLOWED=NO build
npx tsc --noEmit # repo root (filter react_app_sate-ui_update @/… alias noise)

Web app (React + Vite)

cd react_app_sate-ui_update
npm install
npm run dev # vite — local dev server
npm run build # tsc -b && vite build — REQUIRED before pushing (noUnusedLocals)
npm run preview # vite preview — serve the production build locally

Deploy is a git subtree to a separate repo that Vercel builds:

# from repo root
git subtree push --prefix=react_app_sate-ui_update webapp <branch>

:::warning Deploy-breaker The web build runs tsc -b with noUnusedLocals, so a merely-unused variable fails Vercel even though a plain typecheck passes. Always npm run build before pushing the web subtree. :::

Backend

Edge functions validate their own tokens, so they must deploy with --no-verify-jwt:

supabase functions deploy device-api --no-verify-jwt
supabase functions deploy finalize-session --no-verify-jwt
supabase functions deploy mint-plaud-token --no-verify-jwt
  • cf-processor (cf-processor/, Python container): the long-lived AI processor that holds the transcription call — deployed as a Cloudflare Container, never an edge fn.
  • cloudflare/ is the Workers port of the backend.

:::warning Never redeploy with verify_jwt:true The MCP default is verify_jwt:true; redeploying device-api or mint-plaud-token that way breaks recorder registration and Plaud token minting. Always pass --no-verify-jwt. :::

See Backend pipeline.

Documentation site (this site)

cd docs-site
npm install
npm start # docusaurus start — live-reload dev server
npm run build # docusaurus build — static site into build/
npm run serve # serve the production build locally
npm run deploy:cf # build + wrangler pages deploy build --project-name sate-docs

Built with Docusaurus 3.10.2; deployed to Cloudflare Pages at sate-docs.pages.dev.

Hardware testing

Before a firmware release, run the hardware-in-the-loop harness on a real device:

cd hwtest
python3 run.py --sim # self-test the harness, no hardware
python3 run.py --config config.toml # recorder (USB serial)
python3 run.py --target pendant --config config.toml # pendant (BLE)
python3 run.py --list # list scenarios and exit
python3 gui.py # native desktop window
python3 dashboard.py # browser dashboard

See Hardware testing.