Skip to main content

Songbird React UI (react_ui/)

The React frontend provides the full DAW interface for Songbird. It runs inside a JUCE WebView (not a regular browser) and communicates with the C++ backend via native functions.

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)

Project Structure

react_ui/
├── src/
│   ├── App.tsx           — Root component, panel layout, loading state
│   ├── main.tsx          — Entry point, React root mount
│   ├── components/       — UI components (atomic design: atoms → panels)
│   ├── data/             — State management (Zustand stores, bridge, meters)
│   ├── lib/              — Non-React utilities (JUCE bridge, AI, theme)
│   └── assets/           — Static assets
├── index.html            — HTML entry point
├── vite.config.ts        — Vite configuration
├── tsconfig.app.json     — TypeScript config
└── package.json          — Dependencies

Build & Development

cd react_ui
npm install
npm run dev      # Vite dev server (standalone browser development)
npm run build    # Production build → dist/ (served by JUCE WebView)
Important: In production, the React app runs inside the JUCE WebView, not a browser. The window.__JUCE__ global is injected by JUCE and provides the native function bridge. When running npm run dev, bridge calls are no-ops.

WebView Embedding

The C++ backend loads the built React app from react_ui/dist/ via the JUCE WebBrowserComponent. Key implications:
  • No browser APIs: localStorage, fetch to external URLs, and other browser-specific APIs are unavailable or restricted
  • State persistence: All persistence routes through juceBridge (Zustand ↔ C++ ↔ disk)
  • Native functions: Use nativeFunction('name') from @/data/bridge instead of HTTP requests
  • Events: C++ pushes data via emitEventIfBrowserIsVisible()addStateListener() in JS

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)