StoryBooth is a Linux-only Tauri 2 desktop application. React renders a kiosk flow inside the system webview; Rust owns configuration, window behavior, capture, and storage.
- Operate without an internet connection.
- Keep recordings and participant metadata on the booth machine.
- Make event content and hardware deployment-specific rather than source-coded.
- Produce ordinary H.264/AAC MP4 files with readable JSON sidecars.
- Fail visibly when configured hardware is absent.
- Keep the participant flow simple enough for unattended touch use.
src/App.tsx is the state machine:
attract → intro → consent → instructions → tips → record × N → review → thanks
The UI fetches its complete configuration once through the get_config Tauri
command. It does not call browser camera APIs. Other commands start and discard
sessions, control preview and recording, and submit the final manifest.
src-tauri/src/ contains:
config.rs— typed TOML parsing and configuration precedence;capture.rs— device discovery, GStreamer pipelines, preview server, clips;session.rs— per-session directories, submission metadata, cleanup;lib.rs— Tauri commands and fullscreen/monitor behavior.
The production path is native GStreamer:
V4L2 MJPG camera
→ JPEG decode
→ frame-rate normalization
→ tee
├─ JPEG preview → loopback HTTP server → webview
└─ H.264 encoder ┐
PulseAudio microphone ─┴→ AAC → MP4 mux → qN.mp4
Software x264enc is the default. When hardware_encode = true, compatible
systems can use VAAPI H.264 encoding after the required drivers and plugins are
installed.
The loopback server binds to 127.0.0.1. Besides preview frames, it serves
recorded clips with HTTP Range support so WebKitGTK can replay and seek during
final review.
The committed config.example.toml is both schema example and safe compiled
fallback. Real deployment profiles are ignored.
Resolution order:
STORYBOOTH_CONFIG- repo-local
config.tomlin debug builds - the platform app-config
config.toml - embedded
config.example.toml
On a release build's first run, the embedded example is copied to the platform app-config directory. Operators can edit that file without rebuilding.
Participant fields are an array of { key, label, required } records. Submitted
values are stored as a JSON object keyed by the configured stable keys, so the
application does not need deployment-specific metadata structs.
start_session creates a dated working directory. Each retained answer is
written directly as qN.mp4; a re-record replaces that question's file.
- Decline or inactivity: the working directory is removed.
- Submit:
session.jsonis written and the directory becomes a completed session. - Skip: the answer remains in the manifest with no file.
Example:
{
"id": "1785184930123",
"created_at": "2026-07-26T19:22:10-04:00",
"consent_text": "The exact wording displayed to the participant.",
"participant": {
"name": "Jordan",
"organization": "Community archive"
},
"answers": [
{
"question_index": 0,
"question_text": "What experience would you like to share?",
"file": "q1.mp4",
"attempts": 1,
"skipped": false
}
]
}SQLite is not required. The folders and JSON sidecars are the source of truth.
The application only knows a logo web path, accessible label, and accent color.
Generic public branding is committed. Deployment-only files belong under the
ignored public/branding.local/ directory and are included when that deployment
is built.
Because Vite bundles public/, do not publish a build artifact made from a
worktree containing private branding unless that branding is intended for the
artifact.
StoryBooth intentionally has no remote service. Its relevant trust boundary is the local booth machine:
- participant recordings and metadata are sensitive local files;
- the preview HTTP listener is loopback-only;
- configuration may contain client wording and is excluded from version control;
- operators control physical access, exports, retention, and deletion.
See SECURITY.md for deployment responsibilities.