Skip to main content

Songbird React UI (react_ui/)

The React frontend provides the full DAW interface for Songbird. It runs inside a Tauri v2 WebView (not a regular browser) and communicates with the Rust backend through the sync engine — a single typed dispatch (songbird_sync::dispatch) that all UI ↔ backend traffic flows through. The same UI also runs against a headless Rust WebSocket server for browser-based e2e tests and the WASM build for the web app.

Tech Stack

  • React 19 — UI framework
  • Vite — Build tool with @vitejs/plugin-react-swc
  • TypeScript — Type safety
  • Tailwind CSS v4 — Utility-first styling (via @tailwindcss/vite)
  • Zustand — Lightweight state management with persistence
  • Radix UI — Accessible primitive components
  • class-variance-authority — Component variant styling
  • @tonaljs — Music theory (chords, notes, scales)
  • @codemirror — Code editor (ScriptFX, BirdFilePanel)

Project Structure

react_ui/
├── src/
│   ├── App.tsx           — Root component, panel layout, loading state
│   ├── main.tsx          — Entry point, React root mount
│   ├── sentry.ts         — Sentry error tracking initialization
│   ├── components/       — UI components (atomic design: atoms → panels)
│   ├── data/             — State management (Zustand stores, meters, feature flags)
│   ├── sync/             — Sync engine client (api.ts, channels/, transports/, wiring.ts)
│   ├── hooks/            — Custom React hooks (useGhostNotes, useCollabCursorTracking, etc.)
│   ├── lib/              — Non-React utilities (theme, AI agents, GL renderer, native bridges)
│   ├── services/         — External service clients (collabSocket.ts, collabMixerSync.ts)
│   └── assets/           — Static assets
├── index.html            — HTML entry point
├── vite.config.ts        — Vite configuration
├── vitest.config.ts      — Vitest test configuration
├── tsconfig.app.json     — TypeScript config
└── package.json          — Dependencies

Build & Development

The preferred workflow is ./utils/build-rs from the repo root, which boots the Vite dev server and the Tauri shell together with HMR. The raw npm scripts still work:
cd react_ui
npm install
npm run dev      # Vite dev server (standalone browser development)
npm run build    # Production build → dist/ (embedded into the Tauri app)
Important: In production, the React app runs inside Tauri v2’s WebView (or, for songbird-headless / songbird-wasm, against a Rust WebSocket server). All backend calls go through the sync engine — send() / sendRT() from @/sync/api. Direct Tauri invoke() is reserved for OS dialogs only (see Hard Rule #1 in CLAUDE.md).

Backend communication

A single typed pipeline carries every UI ↔ backend message:
  • Commandssend('channel.action', payload) from @/sync/api. High-frequency knob/slider drags use sendRT(...) to skip guards and echo suppression.
  • Events — incoming events are subscribed in @/sync/wiring.ts and the per-channel events.ts modules, then mirrored into Zustand stores.
  • Transports — Tauri IPC in desktop, WebSocket in headless/web, in-process dispatch in WASM. All three share the same dispatch API.
  • Persistence — settings that must survive a reload live in a sync-engine channel with its own PersistenceStrategy. Never localStorage or Zustand’s persist middleware.

Key Conventions

  • Atomic Design: Components organized as atoms → molecules → organisms → panels
  • Tailwind-only styling: No CSS modules or styled-components. Use cn() from @/lib/utils for conditional classes
  • Zustand for state: Never use React Context or prop drilling for shared state
  • Direct DOM for meters: High-frequency visuals (level meters, playheads) use subscribeRtBuffer() + direct DOM manipulation, bypassing React’s render cycle
  • Path aliases: @/ maps to src/ (configured in tsconfig.app.json)

Testing

The React UI uses Vitest for unit testing. Test files live alongside the code they test (e.g., data/slices/mixer.test.ts).
cd react_ui
npx vitest run        # Run all tests once
npx vitest            # Watch mode
Current test coverage includes:
  • Zustand store slices (chat.test.ts, generate.test.ts, mixer.test.ts, transport.test.ts)
  • Store integration tests (store.test.ts)
  • Utility tests (sliderDrag.test.ts)

Recent Additions

  • Inline AI Generation — Option+click-drag on arrangement tracks to generate audio (Lyria) or MIDI directly at a position. Uses GenerateInlinePopup organism and inlineGenerate slice.
  • CollaborationCollabCursors component renders Figma-style multiplayer cursors. CollabPanel shows connected users and invite codes. collabMixerSync.ts provides optimistic mixer state sync.
  • Stock Plugin UIs — Premium-quality plugin editors in components/plugins/ using shared PluginShell/PluginBody/PluginSection layout components and RotaryKnob/ADSRView atoms.
  • ScriptFX EditorScriptFXEditorPanel provides a CodeMirror-based editor for writing DSP scripts (JavaScript or Faust/WASM).
  • Sheet Music & Guitar TabsSheetMusicView and GuitarTabView panels for rendered notation and tablature.
  • Specialized AI Agents — Composer, mix engineer, sound designer, and audio creator agents in lib/ai/agents/ with intent-based routing.
  • Custom Editor FrameworkCustomEditorPanel and EditorPanelShell in panels, with customEditors slice for managing which plugin UIs to render.

Error Tracking

Sentry integration is initialised in sentry.ts for the WebView layer. The Rust side runs its own sentry crate integration (activated by the analytics channel — see rust/crates/data/songbird-analytics/); both feed into the same project.