> ## Documentation Index
> Fetch the complete documentation index at: https://songbird.studiocollective.xyz/llms.txt
> Use this file to discover all available pages before exploring further.

# Sync engine

# Sync Engine

> For humans and LLMs contributing to Songbird.

## Overview

Songbird's **Sync Engine** is the central nervous system of the application. It routes all state changes between the UI, the audio engine, the file system, and collaboration peers. Every meaningful change flows through a single pipeline — guard check → echo suppression → subscriber notification → persistence — and is committed to an in-process Git repo for undo/redo.

The sync engine is **UI-agnostic**: the same core runs whether the frontend is a Tauri WebView (desktop), a browser connecting over WebSocket (headless server), or a CLI script rendering stems on a VM.

```mermaid theme={null}
graph TB
    subgraph Clients["UI Clients"]
        Tauri["Tauri WebView<br/>(Desktop App)"]
        WS["WebSocket Client<br/>(Browser / Remote)"]
        CLI["CLI<br/>(Scripts / VMs)"]
    end

    subgraph Bridge["Bridge Layer"]
        TauriIPC["Tauri IPC<br/>invoke()"]
        WSBridge["WebSocket Bridge<br/>ws://host:port"]
        CLIBridge["Direct Rust API"]
    end

    subgraph SyncEngine["Sync Engine (songbird-sync + songbird-state)"]
        Core["SyncEngineCore<br/>guards → echo → subscribers → persist"]
        Channels["20 Channels<br/>mixer, clip, transport, ml,<br/>plugin, project, recording, engine,<br/>collab, export, separator, slicer,<br/>settings, meters, notifications,<br/>debug, fragments, visual, modular, protocols"]
        Guards["GuardFlags<br/>8 guards controlling update flow"]
        Transports["Transport Layer<br/>DirectEngine, NativeFunction,<br/>WebSocket, Collab, Null"]
    end

    subgraph Engine["Audio Engine (songbird-engine)"]
        Graph["AudioGraph<br/>DAG + topological sort"]
        Transport["Transport<br/>play/stop/seek/loop"]
        Plugins["Plugin Chains<br/>stock DSP + VST3/AU via FFI"]
    end

    subgraph Persistence["Persistence"]
        Files["Files on Disk<br/>daw.bird, daw.mixer.json,<br/>daw.plugins.json, daw.ai.json"]
        Git["Git Repo (libgit2)<br/>refs/heads/main + refs/redo-tip"]
    end

    Tauri --> TauriIPC
    WS --> WSBridge
    CLI --> CLIBridge

    TauriIPC --> Core
    WSBridge --> Core
    CLIBridge --> Core

    Core --> Channels
    Channels --> Guards
    Core --> Transports

    Core -- "apply state" --> Graph
    Core -- "apply state" --> Transport

    Graph -- "state change" --> Channels
    Plugins -- "param change" --> Channels

    Core -- "persist" --> Files
    Core -- "stage + commit" --> Git
    Git -- "undo/redo: restore" --> Files

    style Clients fill:#1a1a2e,stroke:#16213e,color:#e0e0e0
    style Bridge fill:#0d1b2a,stroke:#1b263b,color:#e0e0e0
    style SyncEngine fill:#16213e,stroke:#0f3460,color:#e0e0e0
    style Engine fill:#0f3460,stroke:#533483,color:#e0e0e0
    style Persistence fill:#533483,stroke:#e94560,color:#e0e0e0
```

***

## UI Client Modes

The sync engine supports **multiple UI frontends** connecting to the same Rust engine core. This enables desktop development, remote server deployments, and headless batch processing with the same codebase.

```mermaid theme={null}
graph LR
    subgraph Desktop["Desktop (Tauri)"]
        TauriApp["Tauri v2 Shell"]
        WebView["React WebView"]
    end

    subgraph Headless["Headless (WebSocket)"]
        Server["songbird-headless<br/>ws://host:port"]
        RemoteUI["Remote React UI<br/>(any browser)"]
        Scripts["Automation Scripts"]
    end

    subgraph CLIMode["CLI"]
        CLIBin["songbird-cli"]
    end

    subgraph Core["Shared Engine Core"]
        SE["SyncEngineCore"]
        AE["AudioEngine"]
        State["StateStore"]
    end

    WebView -- "Tauri IPC (invoke)" --> TauriApp
    TauriApp --> SE

    RemoteUI -- "WebSocket (JSON + binary)" --> Server
    Scripts -- "WebSocket" --> Server
    Server --> SE

    CLIBin -- "Direct Rust API" --> SE

    SE --> AE
    SE --> State

    style Desktop fill:#1a1a2e,stroke:#16213e,color:#e0e0e0
    style Headless fill:#0d1b2a,stroke:#1b263b,color:#e0e0e0
    style CLIMode fill:#16213e,stroke:#0f3460,color:#e0e0e0
    style Core fill:#0f3460,stroke:#533483,color:#e0e0e0
```

### Tauri WebView (Desktop)

The primary desktop app. React runs inside Tauri's WebView, communicating with the Rust backend via `invoke()` IPC. This is the full-featured mode with GUI, metering, plugin UIs, and real-time audio.

* **Transport**: Tauri IPC (`window.__SONGBIRD__.invoke()`)
* **Real-time data**: Tauri events at \~30Hz (metering, transport position)
* **Use case**: Day-to-day music production

### WebSocket Headless (Server / VM)

The `songbird-headless` binary runs the full audio engine as a WebSocket server with no GUI. Any WebSocket client — including the same React UI running in a standard browser — can connect and control the engine remotely.

* **Transport**: WebSocket text frames (JSON commands) + binary frames (RT data)
* **Binary frame tags**: `0x01` RT frame, `0x02` audio clip peaks, `0x03` bird mutation
* **Protocol**: Same command/event schema as Tauri IPC — the React UI is backend-agnostic
* **Use case**: Running on cloud VMs, remote collaboration servers, CI/CD render farms, headless recording rigs

```
WebSocket clients (React UI, scripts, tests)
        │
        ▼  ws://host:port
┌──────────────────────────────────────────┐
│  songbird-headless                       │
│  ├─ ServerState (Arc<Mutex<>>)           │
│  │   ├─ StateStore (project model)       │
│  │   └─ EngineSession                    │
│  ├─ command_handler.rs — text frames     │
│  ├─ rt_frame.rs — binary RT broadcast    │
│  └─ broadcast channel → all clients      │
│                                          │
│  audio_engine.rs                         │
│  ├─ cpal audio I/O                       │
│  ├─ ring buffers ↔ engine                │
│  └─ meter polling (~30fps)               │
└──────────────────────────────────────────┘
```

### CLI (Scripted Operations)

The `songbird-cli` binary provides direct Rust API access for batch operations — export, render, validation. No UI, no WebSocket, no audio I/O.

* **Transport**: Direct Rust function calls (no IPC overhead)
* **Use case**: `./songbird-cli render project.bird -o output.wav`, CI pipelines, automated stem exports

***

## Sync Engine Architecture

### SyncEngineCore — `songbird-sync/src/engine_core.rs`

Central router for all state updates. Every inbound event flows through a fixed pipeline:

```mermaid theme={null}
graph LR
    A["Inbound Event<br/>(React / Engine / Collab)"] --> B["Guard Check<br/>(allow/block)"]
    B --> C["Echo Check<br/>(version-counter or json-compare)"]
    C --> D["Subscribers<br/>(dispatch to handlers)"]
    D --> E["Persist<br/>(git-tracked or session)"]
    E --> F["EventBus<br/>(component-level pub/sub)"]

    style A fill:#e94560,stroke:#e94560,color:#fff
    style B fill:#533483,stroke:#533483,color:#fff
    style C fill:#0f3460,stroke:#0f3460,color:#fff
    style D fill:#16213e,stroke:#16213e,color:#e0e0e0
    style E fill:#1a1a2e,stroke:#1a1a2e,color:#e0e0e0
    style F fill:#0d1b2a,stroke:#0d1b2a,color:#e0e0e0
```

### Channels — `songbird-sync/src/channels/`

Twenty domain channels, each defined as its own submodule under `channels/` with a `defs.rs` (events / command names / wiring) and `commands.rs` (dispatch arms). Each channel specifies its authority and event list:

| Channel         | Authority | Key Events                                                                                                      |
| --------------- | --------- | --------------------------------------------------------------------------------------------------------------- |
| `mixer`         | Shared    | `mixer:track_state`, `mixer:notes_changed`, `mixer:audio_clip_peaks`, `mixer:track_mixer_update`, `mixer:state` |
| `clip`          | Shared    | `clip:content_changed`, `clip:history_changed` (replaces the old `bird` channel)                                |
| `transport`     | Shared    | `transport:state`, `transport:link_status_changed`                                                              |
| `plugin`        | Engine    | `plugin:state`                                                                                                  |
| `ml`            | React     | `ml:lyria`, `ml:generate`, `ml:generate_progress`, `ml:generate_complete` (replaces the old `ai` channel)       |
| `recording`     | Shared    | `recording:track_added`, `recording:take_lane_changed`, … (took over `track:*`)                                 |
| `engine`        | Engine    | engine lifecycle, device events, transport-clock                                                                |
| `collab`        | Shared    | collab session, cursor positions, peer presence                                                                 |
| `export`        | Engine    | offline render progress / stage events                                                                          |
| `separator`     | Engine    | stem-separation job progress                                                                                    |
| `slicer`        | Engine    | one-shot slicing operations                                                                                     |
| `settings`      | React     | `settings:changed`, `settings:notes`                                                                            |
| `meters`        | Engine    | `meters:rt_frame`                                                                                               |
| `notifications` | Engine    | toast log, loading progress, etc.                                                                               |
| `debug`         | Engine    | debug overlays, instrumentation hooks                                                                           |
| `fragments`     | Shared    | fragment-browser indexing & previews                                                                            |
| `visual`        | Engine    | GL surface state, render hints                                                                                  |
| `modular`       | Shared    | Songbird Modular node-graph editor                                                                              |
| `protocols`     | Engine    | external protocols (OSC, MIDI 2.0, etc.)                                                                        |
| `project`       | Shared    | `project:state`, `project:section_added`, `project:section_deleted`, `project:section_reordered`                |

The TS side additionally configures echo strategy, persistence, and guard flags per channel (see `react_ui/src/sync/channels/`). Each TS channel file also defines typed commands via `XCommands` interfaces and `buildXCommands()` factories.

### Naming Conventions

| Type                   | Format               | Examples                                                                               |
| ---------------------- | -------------------- | -------------------------------------------------------------------------------------- |
| Events (Engine → JS)   | `channel:snake_case` | `mixer:track_state`, `transport:link_status_changed`, `notifications:loading_progress` |
| Commands (JS → Engine) | `channel.snake_case` | `mixer.view_mode`, `transport.set_bpm`, `recording.midi_arm`                           |
| JS method names        | camelCase            | `viewMode()`, `setBpm()`, `midiArm()`                                                  |
| Rust method names      | snake\_case          | `view_mode()`, `set_bpm()`, `midi_arm()`                                               |

Command strings use snake\_case for legibility, but JS handler method names remain camelCase per JS convention. The `resolveCommand()` function in `commandMap.ts` converts `'mixer.view_mode'` → `['mixer', 'viewMode']` for handler lookup.

Sub-domain commands (e.g., `clip.delete`, `take_lane.add`, `recording.midi_arm`, `section.add`) route to their parent channel (track or project) with a prefixed lookup key (e.g., `'clip:delete'`, `'take_lane:add'`).

### Per-Channel Reference

Below is the complete list of events and commands for each channel, grouped by direction.

***

#### `mixer` — Track Audio Parameters

| Direction                  | Name                       | Description                                                                 |
| -------------------------- | -------------------------- | --------------------------------------------------------------------------- |
| **Events (Engine → JS)**   |                            |                                                                             |
|                            | `mixer:track_state`        | Full track state from engine (bird file load)                               |
|                            | `mixer:notes_changed`      | Lightweight MIDI note deltas                                                |
|                            | `mixer:audio_clip_peaks`   | Async waveform peak data per clip                                           |
|                            | `mixer:track_mixer_update` | Per-track volume/pan/mute/solo from engine                                  |
|                            | `mixer:state`              | Full mixer state sync                                                       |
| **Commands (JS → Engine)** |                            |                                                                             |
|                            | `mixer.volume`             | Set track volume (`trackIdx`, `param`, `value`)                             |
|                            | `mixer.pan`                | Set track pan (`trackIdx`, `param`, `value`)                                |
|                            | `mixer.mute`               | Toggle track mute (`trackIdx`, `value`)                                     |
|                            | `mixer.solo`               | Toggle track solo (`trackIdx`, `value`)                                     |
|                            | `mixer.view_mode`          | Set mixer view mode (`value`)                                               |
|                            | `mixer.melodyne`           | Close Melodyne overlay                                                      |
|                            | `mixer.keyboard`           | Toggle MIDI keyboard mode (`value`)                                         |
|                            | `mixer.send_level`         | Set send level (`trackIdx`, `param`, `value`)                               |
|                            | `mixer.send_mode`          | Set send mode (`trackIdx`, `value`)                                         |
|                            | `mixer.sidechain`          | Set sidechain source (`trackIdx`, `value`)                                  |
|                            | `mixer.plugin_param`       | Set plugin parameter RT (`trackIdx`, `param`, `value`)                      |
|                            | `mixer.set_track_mixer`    | Batch set track mixer state (`trackIdx`, `volumeDb`, `pan`, `mute`, `solo`) |

***

#### `transport` — Playback & Timing

| Direction                  | Name                                    | Description                                            |
| -------------------------- | --------------------------------------- | ------------------------------------------------------ |
| **Events (Engine → JS)**   |                                         |                                                        |
|                            | `transport:state`                       | Full transport state (BPM, position, key, scale, loop) |
|                            | `transport:link_status_changed`         | Ableton Link peer status update                        |
| **Commands (JS → Engine)** |                                         |                                                        |
|                            | `transport.play`                        | Start playback                                         |
|                            | `transport.pause`                       | Pause playback                                         |
|                            | `transport.stop`                        | Stop and return to start                               |
|                            | `transport.record`                      | Toggle recording (`value`)                             |
|                            | `transport.scrub`                       | Scrub playhead (`value`)                               |
|                            | `transport.position`                    | Set playhead position RT (`value`)                     |
|                            | `transport.set_bpm`                     | Set tempo (`bpm`)                                      |
|                            | `transport.set_looping`                 | Toggle loop (`value`)                                  |
|                            | `transport.set_loop_range`              | Set loop bounds (`startBar`, `endBar`)                 |
|                            | `transport.enable_link`                 | Toggle Ableton Link (`value`)                          |
|                            | `transport.enable_link_start_stop_sync` | Toggle Link start/stop sync (`value`)                  |
|                            | `transport.set_link_custom_offset`      | Set Link custom offset (`ms`)                          |

***

#### `recording` — Track Management & Recording (+ sub-domains: `clip`, `take_lane`)

> **Renamed from `track`.** The standalone `clip`, `take_lane`, and
> `recording` namespaces now route through this channel. The legacy
> `track:*` event names have moved under `recording:*`.

| Direction                  | Name                          | Description                                           |
| -------------------------- | ----------------------------- | ----------------------------------------------------- |
| **Events (Engine → JS)**   |                               |                                                       |
|                            | `recording:track_added`       | A track was added                                     |
|                            | `recording:track_removed`     | A track was removed                                   |
|                            | `recording:track_renamed`     | A track was renamed                                   |
|                            | `recording:track_reordered`   | Track order changed                                   |
|                            | `recording:cleared`           | Recorded content cleared                              |
|                            | `recording:take_lane_changed` | Take lane added or auditioned                         |
| **Commands (JS → Engine)** |                               |                                                       |
|                            | `recording.add_audio_track`   | Create new audio track                                |
|                            | `recording.add_midi_track`    | Create new MIDI track                                 |
|                            | `recording.remove_track`      | Remove track (`trackIdx`)                             |
|                            | `recording.rename_track`      | Rename track (`trackIdx`, `value`)                    |
|                            | `recording.clear_recorded`    | Clear recorded MIDI (`trackIdx`)                      |
|                            | `recording.reorder_tracks`    | Reorder tracks (`value`: JSON array of IDs)           |
|                            | `clip.delete`                 | Delete audio clip (`trackIdx`, `param`)               |
|                            | `take_lane.add`               | Add take lane (`trackIdx`)                            |
|                            | `take_lane.audition`          | Audition take lane (`trackIdx`, `value`)              |
|                            | `recording.midi_arm`          | Toggle MIDI record arm (`trackIdx`, `value`)          |
|                            | `recording.audio_arm`         | Toggle audio record arm (`trackIdx`, `value`)         |
|                            | `recording.audio_source`      | Set audio input source (`trackIdx`, `param`, `value`) |
|                            | `recording.midi_input`        | Set MIDI input device (`trackIdx`, `value`)           |
|                            | `recording.monitor`           | Set input monitoring mode (`trackIdx`, `value`)       |

***

#### `project` — Project Settings & Sections (+ sub-domain: `section`)

| Direction                  | Name                        | Description                                                                       |
| -------------------------- | --------------------------- | --------------------------------------------------------------------------------- |
| **Events (Engine → JS)**   |                             |                                                                                   |
|                            | `project:state`             | Full project settings (BPM, key, scale, time sig)                                 |
|                            | `project:section_added`     | A section was added                                                               |
|                            | `project:section_deleted`   | A section was deleted                                                             |
|                            | `project:section_reordered` | Section order changed                                                             |
| **Commands (JS → Engine)** |                             |                                                                                   |
|                            | `section.add`               | Add section (`param`: name, `value`: bar count) — routes to project channel       |
|                            | `section.delete`            | Delete section (`param`: name) — routes to project channel                        |
|                            | `section.rename`            | Rename section (`param`: old name, `value`: new name) — routes to project channel |
|                            | `section.reorder`           | Reorder sections (`value`: JSON array) — routes to project channel                |

***

#### `clip` — Clip Content (replaces the old `bird` channel)

| Direction                | Name                   | Description                                                                                                                      |
| ------------------------ | ---------------------- | -------------------------------------------------------------------------------------------------------------------------------- |
| **Events (Engine → JS)** |                        |                                                                                                                                  |
|                          | `clip:updated`         | Audio/MIDI clip content updated (replaces `bird:content_changed`)                                                                |
|                          | `clip:history_changed` | Git history changed (undo/redo, new commit)                                                                                      |
| **Commands**             |                        | Clip mutations route through this channel; see `rust/crates/data/songbird-sync/src/channels/clip/commands.rs` for the full list. |

***

#### `ml` — ML / AI Generation (replaces the old `ai` channel)

Covers Lyria music generation, Veo video generation, local LLM chat
inference, and model-download progress.

| Direction                  | Name                                                                                         | Description                                                       |
| -------------------------- | -------------------------------------------------------------------------------------------- | ----------------------------------------------------------------- |
| **Events (Engine → JS)**   |                                                                                              |                                                                   |
|                            | `ml:lyria`                                                                                   | Lyria music generation config state                               |
|                            | `ml:generate`                                                                                | Generate job state                                                |
|                            | `ml:generate_progress`                                                                       | Generation progress (ephemeral)                                   |
|                            | `ml:generate_complete`                                                                       | Generation completed (ephemeral)                                  |
|                            | `ml:veo_progress` / `ml:veo_complete`                                                        | Veo video generation progress / completion                        |
|                            | `ml:chat_chunk`, `ml:chat_thinking`, `ml:chat_tool_use`, `ml:chat_complete`, `ml:chat_error` | LLM chat streaming events                                         |
|                            | `ml:chat_model_download_*` / `ml:model_download_*`                                           | Local-model download progress                                     |
|                            | `ml:thread_summary`, `ml:macro_classification`                                               | Async LLM follow-ups                                              |
| **Commands (JS → Engine)** |                                                                                              | See `rust/crates/data/songbird-sync/src/channels/ml/commands.rs`. |

***

#### `plugin` — Plugin State

| Direction                  | Name            | Description                                                |
| -------------------------- | --------------- | ---------------------------------------------------------- |
| **Events (Engine → JS)**   |                 |                                                            |
|                            | `plugin:state`  | Plugin parameter/state update from engine                  |
| **Commands (JS → Engine)** |                 |                                                            |
|                            | `plugin.change` | Swap plugin on a track slot (`trackIdx`, `param`, `value`) |
|                            | `plugin.bypass` | Toggle plugin bypass (`trackIdx`, `param`, `value`)        |
|                            | `plugin.open`   | Open plugin editor window (`trackIdx`, `param`)            |

***

#### `chat` — AI Chat State

| Direction                | Name         | Description                                                                     |
| ------------------------ | ------------ | ------------------------------------------------------------------------------- |
| **Events (Engine → JS)** |              |                                                                                 |
|                          | `chat:state` | Full chat state sync (messages, threads, panel visibility)                      |
| **Commands**             |              | *None — chat state is written directly to the Zustand store, not via commands.* |

***

#### `settings` — App Settings

| Direction                | Name               | Description                                                           |
| ------------------------ | ------------------ | --------------------------------------------------------------------- |
| **Events (Engine → JS)** |                    |                                                                       |
|                          | `settings:changed` | Settings update from engine                                           |
|                          | `settings:notes`   | Notes/comments state update (undo/redo, collab)                       |
| **Commands**             |                    | *None — settings are persisted directly via the StateStorage bridge.* |

***

#### `notifications` — Ephemeral Engine Notifications (fire-and-forget)

| Direction                | Name                                    | Description                                               |
| ------------------------ | --------------------------------------- | --------------------------------------------------------- |
| **Events (Engine → JS)** |                                         |                                                           |
|                          | `notifications:log`                     | Debug log forwarding from engine to browser console       |
|                          | `notifications:dropout_detected`        | Audio dropout/glitch warning                              |
|                          | `notifications:loading_progress`        | Project loading progress                                  |
|                          | `notifications:show_project_picker`     | Signal to show project picker UI                          |
|                          | `notifications:recording_started`       | Recording session started                                 |
|                          | `notifications:recording_stopped`       | Recording session stopped                                 |
|                          | `notifications:live_note_on`            | Live MIDI note on (ghost notes, activity)                 |
|                          | `notifications:live_note_off`           | Live MIDI note off                                        |
|                          | `notifications:terminal_output`         | Terminal process output streaming                         |
|                          | `notifications:export_progress`         | Stem export progress                                      |
|                          | `notifications:export_done`             | Stem export completed                                     |
|                          | `notifications:melodyne_overlay_opened` | Melodyne ARA overlay opened                               |
|                          | `notifications:melodyne_overlay_closed` | Melodyne ARA overlay closed                               |
| **Commands**             |                                         | *None — notifications are one-directional (Engine → JS).* |

***

#### `meters` — Real-Time Audio Metering

| Direction                | Name              | Description                                                               |
| ------------------------ | ----------------- | ------------------------------------------------------------------------- |
| **Events (Engine → JS)** |                   |                                                                           |
|                          | `meters:rt_frame` | Real-time meter data at \~30Hz (levels, spectrum, stereo, CPU, position)  |
| **Commands**             |                   | *None — meters are read-only from JS. Data arrives via binary transport.* |

***

### GuardFlags — `songbird-sync/src/guards.rs`

Shared state gates that block updates in specific scenarios:

| Guard                   | Purpose                                 |
| ----------------------- | --------------------------------------- |
| `loadFinished`          | Block until project loading is complete |
| `notUndoRedo`           | Block echo during undo/redo operations  |
| `notMidiEditing`        | Block during MIDI edit transactions     |
| `notStreaming`          | Block during AI chat streaming          |
| `notSliderDragging`     | Block persist during slider gestures    |
| `notHydrating`          | Prevent updates during initialization   |
| `notLoopCoolingDown`    | Debounce loop range changes             |
| `notLoopRangeOwnedByUi` | UI owns loop range during drag          |

Guards are implemented as `Arc<AtomicBool>` flags in Rust, providing thread-safe, lock-free gating. Each channel specifies which guards must pass before an update is accepted.

### UpdateSource

Every change is tagged with its origin for echo suppression:

| Source        | Description                   |
| ------------- | ----------------------------- |
| `React`       | UI action (user interaction)  |
| `Engine`      | Audio engine state change     |
| `Collab`      | Remote peer via collaboration |
| `FileWatcher` | Git file change on disk       |

***

## Transport Layer

The sync engine uses **trait-based transports** to abstract how data flows between system components. This is what enables the same engine to serve Tauri, WebSocket, and CLI frontends.

### Transport Trait

```rust theme={null}
pub trait Transport: Send + Sync {
    fn id(&self) -> &str;
    fn send_event(&self, channel: &str, payload: &serde_json::Value);
    fn on_receive(&self, channel: &str, handler: ...) -> SubscriptionId;
    fn unsubscribe(&self, id: SubscriptionId);
}

// Extended for binary frames (RT data, waveform peaks)
pub trait BinaryTransport: Transport {
    fn send_binary(&self, channel: &str, buffer: &[u8]);
    fn on_receive_binary(&self, channel: &str, handler: ...) -> SubscriptionId;
}
```

### Transport Implementations

| Transport                 | Direction     | Description                                                                                                                |
| ------------------------- | ------------- | -------------------------------------------------------------------------------------------------------------------------- |
| `DirectEngineTransport`   | Rust → Engine | Zero-serialization hot path for continuous gestures (slider drags, scrub). Pre-resolves dispatcher functions at init time. |
| `NativeFunctionTransport` | Rust → Engine | Structured state commits via native function bridge. Maps channels to native function names.                               |
| `WebSocketTransport`      | Bidirectional | Text frames (JSON) + binary frames with tag-byte routing. Supports the headless server and browser WebSocket connections.  |
| `CollabTransport`         | Bidirectional | Remote sync via collab server WebSocket. Handles connect/disconnect lifecycle and incoming message dispatch.               |
| `NullTransport`           | No-op         | For channels that don't use a particular direction.                                                                        |

### Binary Frame Protocol

Binary frames use a **tag-byte prefix** for zero-overhead routing:

| Tag    | Name             | Content                                               |
| ------ | ---------------- | ----------------------------------------------------- |
| `0x01` | RT Frame         | Metering, transport position, spectrum data (\~30fps) |
| `0x02` | Audio Clip Peaks | Waveform peak data for display                        |
| `0x03` | Bird Mutation    | Result of a `.bird` file edit                         |

The `WebSocketTransport` automatically decodes binary frames via tag routing and dispatches to both binary and text (parsed JSON) handlers.

***

## Files on Disk

Each project directory contains:

| File               | Tracked | Purpose                                                     |
| ------------------ | ------- | ----------------------------------------------------------- |
| `daw.bird`         | ✅ Git   | Composition — notes, arrangement, structure                 |
| `daw.mixer.json`   | ✅ Git   | Mixer state — volumes, pans, mutes, solos, sends            |
| `daw.state.json`   | ✅ Git   | Project state — transport, markers, tempo/key/time-sig maps |
| `daw.plugins.json` | ✅ Git   | Plugin state — VST3/AU presets as structured JSON           |
| `daw.ai.json`      | ✅ Git   | AI state — chat threads, Lyria config, generation history   |

**Rule**: All `daw.*` files are git-tracked and participate in undo/redo.

***

## Bridge Layer — `songbird-state/src/bridge_layer.rs`

The bridge layer sits between the UI stores and the sync engine, handling persistence gating and mode detection.

### BridgeMode

| Mode        | Description                                                              |
| ----------- | ------------------------------------------------------------------------ |
| `Native`    | Running inside Tauri WebView — uses `window.__SONGBIRD__` native interop |
| `WebSocket` | Running in a standard browser — uses WebSocket bridge                    |

### PersistGate

Centralized gating logic that decides whether a store's `setItem` call should proceed to disk:

1. **Hydration gate** — During initial load, don't persist back (data just came from backend)
2. **Slider drag gate** — During mixer slider drags, suppress mixer persist (audio feedback via RT bypass)
3. **Streaming gate** — During AI chat streaming, suppress chat persist (flush at end)

### SliderDragGuard

Per-fader persist suppression using an `AtomicU32` counter:

* `begin_drag()` → increments counter
* `end_drag()` → decrements counter, returns `true` when all drags ended
* `is_dragging()` → `true` while any slider is active

***

## Zustand Stores (React)

Persisted stores synced to the Rust backend:

| Store               | ID                | Persisted To     |
| ------------------- | ----------------- | ---------------- |
| `useTransportStore` | `transport:state` | `daw.state.json` |
| `useMixerStore`     | `mixer:state`     | `daw.mixer.json` |
| `useChatStore`      | `chat:state`      | `daw.ai.json`    |
| `useGenerateStore`  | `ml:generate`     | `daw.ai.json`    |

### Persist Flow

```
React setState → Zustand persist middleware → Bridge Layer
  → PersistGate check → Sync Engine → guard check → echo check → subscribers → persist
```

### Hydration Flow (Load)

```
Rust loads state files from disk
  → Zustand persist getItem → IPC → Rust returns cached JSON
  → Store hydrates synchronously
  → onRehydrateStorage callback → counter++ → all stores done → reactReady()
```

***

## Real-Time Events (Engine → React)

High-frequency telemetry data (audio levels, transport position, stereo analysis, CPU stats) bypasses the standard state sync to minimize overhead.

```mermaid theme={null}
graph LR
    A["Audio Engine<br/>(~30Hz)"] --> B["Batched Payload<br/>(binary RT frame)"]
    B --> C["rtBuffer<br/>(direct-DOM, no React)"]
    C --> D["requestAnimationFrame<br/>(ballistic smoothing)"]
    D --> E["Canvas / DOM nodes<br/>(direct subscribers)"]
    D --> F["Zustand (~20Hz)<br/>(React components)"]

    style A fill:#e94560,stroke:#e94560,color:#fff
    style B fill:#533483,stroke:#533483,color:#fff
    style C fill:#0f3460,stroke:#0f3460,color:#fff
    style D fill:#16213e,stroke:#16213e,color:#e0e0e0
    style E fill:#1a1a2e,stroke:#1a1a2e,color:#e0e0e0
    style F fill:#0d1b2a,stroke:#0d1b2a,color:#e0e0e0
```

1. **Batched Rust to JS**: Real-time telemetry is grouped into a single payload and emitted at \~30Hz.
2. **Direct-DOM Buffer (`rtBuffer`)**: Data is written directly to a shared mutable object (`getRtBuffer()`). This avoids React re-renders completely.
3. **Ballistic Smoothing**: A single `requestAnimationFrame` loop handles smoothing (e.g. meter decay) and notifies direct subscribers (canvas, plain DOM nodes).
4. **Zustand Throttling**: The full Zustand store (`useMeterStore`) is only updated every N frames (\~20Hz) for React components that need reactive bindings.
5. **DirectEngineTransport**: Slider drags use a real-time bypass path that skips guards and persistence, writing directly to the engine.

### Smoothing Constants

| Constant           | Value | Purpose                 |
| ------------------ | ----- | ----------------------- |
| `LEVEL_RELEASE`    | 0.78  | Meter decay rate        |
| `SPECTRUM_RELEASE` | 0.68  | Spectrum analyzer decay |
| `STEREO_SMOOTH`    | 0.72  | Stereo field smoothing  |
| `CPU_SMOOTH`       | 0.90  | CPU usage smoothing     |

***

## Loading Sequence

```
1. App launches (Tauri or headless WebSocket server)
2. uiReady() from React → start background loading
3. Scan for plugins (stock + VST3/AU via FFI)
4. Load .bird file
   - bird_tokenizer → bird_parser → bird_populator → EngineSession
   - Load plugins, build mixer state
   - Apply mixer state to engine
5. Push track state to React via events
6. Save state cache → defer commit until plugins settle
7. [Meanwhile] React hydrates stores → reactReady() → sets store_hydrated
8. Plugins settle
9. Commit "Project loaded" → commits now enabled
```

### GuardFlags During Load

| Flag                    | Set When                 | Purpose                                      |
| ----------------------- | ------------------------ | -------------------------------------------- |
| `store_hydrated`        | All React stores hydrate | Gates ALL state commits                      |
| `hydrating`             | During initial hydration | Prevents updates during initialization       |
| `undo_redo_in_progress` | During undo/redo         | Blocks echo commits during state restoration |
| `slider_dragging`       | During slider gesture    | Blocks persist until release                 |

***

## Undo/Redo System — `songbird-state/src/undo_redo.rs`

Uses **libgit2** (via the `git2` crate, in-process, zero fork) for git operations.

### Branch Structure

* `refs/heads/main` — current position (HEAD), moves on undo/redo
* `refs/redo-tip` — created on first undo, points to the "newest" undone commit

### Operations

**Commit**:

1. Check for uncommitted changes — skip if working tree is clean
2. Delete `refs/redo-tip` (new change invalidates redo)
3. Create commit on `main`
4. Emit `clip:history_changed` to React

**Undo**:

1. Block if HEAD message contains "Project loaded" or "Initial project state"
2. If no `refs/redo-tip`, create it pointing to current HEAD
3. Get HEAD's parent commit
4. Diff HEAD vs parent → get changed files
5. Restore working directory from parent
6. Move `refs/heads/main` to parent

**Redo**:

1. Look up `refs/redo-tip` — if absent, nothing to redo
2. Walk backward from redo-tip to find the child of current HEAD
3. Diff HEAD vs child → get changed files
4. Restore working directory from child
5. Move `refs/heads/main` to child
6. If HEAD now equals redo-tip, delete the ref

### Undo/Redo Orchestration

1. Set `undo_redo_in_progress` guard flag
2. Flush pending state to disk
3. Perform undo/redo via git2
4. Reload changed files:
   * `.bird` → re-parse via songbird-clips pipeline
   * `.plugins.json` → restore plugin state
   * `.mixer.json` → reload mixer and apply to engine + push to React
5. Emit `clip:history_changed` to update UI
6. Clear `undo_redo_in_progress` guard (blocks React persist echoes)

***

## Commit Sources

Every commit message is tagged with a source:

| Tag       | Source             | Example                          |
| --------- | ------------------ | -------------------------------- |
| `[auto]`  | System             | `[auto] Project loaded`          |
| `[mixer]` | Fader/knob change  | `[mixer] 'drums' vol 80→65`      |
| `[LLM]`   | AI copilot         | `[LLM] Pre-LLM state`            |
| `[user]`  | Manual save/revert | `[user] Reverted last AI change` |

***

## Echo Prevention (Critical)

The sync engine uses multiple mechanisms to prevent feedback loops:

```mermaid theme={null}
graph TD
    A["Inbound Update"] --> B{"Version Counter<br/>already seen?"}
    B -- Yes --> X["❌ Suppressed"]
    B -- No --> C{"JSON Compare<br/>matches cached?"}
    C -- Yes --> X
    C -- No --> D{"Guard Flags<br/>undo/hydrating/drag?"}
    D -- Blocked --> X
    D -- Pass --> E{"UpdateSource<br/>filter by origin"}
    E -- Skip --> X
    E -- Accept --> F["✅ Process Update"]

    style X fill:#e94560,stroke:#e94560,color:#fff
    style F fill:#16213e,stroke:#16213e,color:#e0e0e0
```

1. **Version counter echo** — Each channel tracks a monotonic version; subscribers skip updates they've already seen
2. **JSON compare echo** — For channels using `JsonCompare` strategy, incoming JSON is compared against cached state (with configurable field rounding for floats like volume/pan)
3. **`undo_redo_in_progress` guard** — Blocks all channel commits during undo/redo
4. **`store_hydrated` guard** — Blocks ALL commits until initial hydration completes
5. **`slider_dragging` guard** — Blocks persistence while user is dragging (uses `SliderDragGuard` atomic counter)
6. **UpdateSource tagging** — Each change carries its origin (React, Engine, Collab); subscribers can filter by source
7. **PersistGate** — Centralized check that gates disk writes on hydration, slider drag, and streaming state

***

## History Panel — React

`HistoryPanel.tsx` displays live git history in `git log --oneline` format.

* Fetches via `getHistory` IPC command (reads git via git2 revwalk)
* Auto-refreshes on `clip:history_changed` events
* Expandable toggle at bottom of app

***

## Key Files

### Sync Engine (Rust — songbird-sync)

| File                                                         | Role                                                                                                                                                                                                                                 |
| ------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `rust/crates/data/songbird-sync/src/engine_core.rs`          | Central state router: guards → echo → subscribers → persist                                                                                                                                                                          |
| `rust/crates/data/songbird-sync/src/engine.rs`               | Top-level SyncEngine with connect/disconnect lifecycle                                                                                                                                                                               |
| `rust/crates/data/songbird-sync/src/dispatch.rs`             | Top-level dispatcher — routes `"channel.command"` strings to per-channel `commands::dispatch()`                                                                                                                                      |
| `rust/crates/data/songbird-sync/src/channels/`               | Per-channel definitions and dispatch (20 channels: mixer, clip, transport, ml, plugin, project, recording, engine, collab, export, separator, slicer, settings, meters, notifications, debug, fragments, visual, modular, protocols) |
| `rust/crates/data/songbird-sync/src/wiring.rs`               | Event routing table + WiredSyncEngine                                                                                                                                                                                                |
| `rust/crates/data/songbird-sync/src/wiring_orchestration.rs` | Orchestrate wiring across multiple components                                                                                                                                                                                        |
| `rust/crates/data/songbird-sync/src/transports.rs`           | Transport trait + implementations (DirectEngine, NativeFunction, WebSocket, Collab, Null)                                                                                                                                            |
| `rust/crates/data/songbird-sync/src/profiler.rs`             | Channel-aware performance profiler                                                                                                                                                                                                   |
| `rust/crates/data/songbird-sync/src/batch_throttle.rs`       | Batch and throttle middleware for high-frequency events                                                                                                                                                                              |

### State Management (Rust — songbird-state)

The state crate now lives at `rust/crates/data/songbird-state/`. Its
public surface is the `StateManager` (channel-aligned slices), the
`bridges/` layer (slider-drag guard, persist gate, WS bridge protocol),
and the `persistence/` layer (bird IO, clips IO, git-based session
history). See its `SPEC.md` for the full pipeline.

| File                                                                 | Role                                                        |
| -------------------------------------------------------------------- | ----------------------------------------------------------- |
| `rust/crates/data/songbird-state/src/state_manager.rs`               | Root state container with channel-aligned slices            |
| `rust/crates/data/songbird-state/src/slices/`                        | Per-channel slice types (song, mixer, plugin, transport, …) |
| `rust/crates/data/songbird-state/src/bridges/bridge_layer.rs`        | BridgeMode, SliderDragGuard, PersistGate, WsBridgeProtocol  |
| `rust/crates/data/songbird-state/src/persistence/bird_io.rs`         | `.bird` text read/write                                     |
| `rust/crates/data/songbird-state/src/persistence/project_history.rs` | Git-based undo/redo via git2                                |
| `rust/crates/data/songbird-state/src/persistence/session.rs`         | Session loading and project lifecycle                       |

### Headless & CLI (Rust)

| File                                                       | Role                                    |
| ---------------------------------------------------------- | --------------------------------------- |
| `rust/crates/app/songbird-headless/src/main.rs`            | WebSocket server entry point            |
| `rust/crates/app/songbird-headless/src/command_handler.rs` | Processes text-frame commands           |
| `rust/crates/app/songbird-headless/src/rt_frame.rs`        | Binary RT frame encoding + broadcast    |
| `rust/crates/app/songbird-cli/src/main.rs`                 | CLI entry point for scripted operations |

### React UI

| File                                              | Role                                                                      |
| ------------------------------------------------- | ------------------------------------------------------------------------- |
| `react_ui/src/sync/engine.ts`                     | Sync engine initialization + typed `send()`                               |
| `react_ui/src/sync/commandMap.ts`                 | `CommandMap` union type, `resolveCommand()` domain→channel router         |
| `react_ui/src/sync/api.ts`                        | Clean `send()` and `sendRT()` helpers                                     |
| `react_ui/src/sync/channels/`                     | Per-channel definitions (XCommands interfaces + buildXCommands factories) |
| `react_ui/src/sync/wiring.ts`                     | Channel registration + transport wiring                                   |
| `react_ui/src/sync/transports/`                   | DirectEngine + WebSocket transport implementations                        |
| `react_ui/src/data/store.ts`                      | Zustand stores, hydration tracking, event listeners                       |
| `react_ui/src/data/meters.ts`                     | Real-time batched event store, RT Buffer, ballistic smoothing             |
| `react_ui/src/data/slices/mixer.ts`               | Mixer state actions (setVolume, setPan with rounding)                     |
| `react_ui/src/components/panels/HistoryPanel.tsx` | Git log UI                                                                |

### Tauri App (Rust)

| File                                                | Role                                             |
| --------------------------------------------------- | ------------------------------------------------ |
| `rust/crates/app/songbird-app/src/main.rs`          | Tauri IPC command handlers                       |
| `rust/crates/app/songbird-app/src/native_invoke.rs` | Tauri invoke bridge (loadState, command routing) |
| `rust/crates/app/songbird-app/src/emit_state.rs`    | Push state updates from engine to React          |
