# How Meetily's SidebarContext Synchronizes React State with Rust Recording and Transcript State

> Discover how Meetily's SidebarContext synchronizes React state with Rust recording and transcript state using a dual context architecture for seamless UI updates. Learn more.

- Repository: [Zackriya Solutions/meetily](https://github.com/Zackriya-Solutions/meetily)
- Tags: internals
- Published: 2026-07-12

---

**Meetily uses a two-tier context architecture where `RecordingStateContext` mirrors Rust's recording lifecycle through Tauri events and polling, while `SidebarProvider` consumes this state to drive UI actions.**

Meetily is an open-source meeting recorder built with a Rust backend and React frontend. Keeping the sidebar UI in sync with the native recording state requires bridging the async gap between Tauri's command layer and React's component lifecycle. The synchronization relies on `RecordingStateContext` acting as the single source of truth for recording flags, while `SidebarProvider` translates these flags into navigation and toggle behaviors.

## The Architecture: From Rust Backend to React UI

The state flow follows a strict unidirectional pattern: Rust emits events → `recordingService` captures them → `RecordingStateContext` updates React state → `SidebarProvider` renders the correct UI.

### RecordingStateContext as the Source of Truth

Located in [`frontend/src/contexts/RecordingStateContext.tsx`](https://github.com/Zackriya-Solutions/meetily/blob/main/frontend/src/contexts/RecordingStateContext.tsx), this global context maintains the canonical recording state. It exposes derived booleans like `isRecording`, `isPaused`, `isStopping`, and `isSaving` that the entire application consumes.

The context initializes by calling `recordingService.getRecordingState()` (lines 89-99) to hydrate React state with the current Rust backend status. This prevents the "page-refresh desync" bug where React loses track of an active recording after reload.

### SidebarProvider as the UI Consumer

The `SidebarProvider` component in [`frontend/src/components/Sidebar/SidebarProvider.tsx`](https://github.com/Zackriya-Solutions/meetily/blob/main/frontend/src/components/Sidebar/SidebarProvider.tsx) imports `useRecordingState` (line 7) to access these flags. It uses `isRecording` to determine whether to show the "Start Recording" button or enable the "Stop" controls.

The `handleRecordingToggle` function (lines 45-64) reads `isRecording` before deciding whether to dispatch a custom DOM event (`start-recording-from-sidebar`) or navigate to the home page with an `autoStartRecording` flag in `sessionStorage`.

## Initial State Hydration and Event-Driven Updates

Meetily employs three synchronization strategies to ensure the React sidebar never drifts from the Rust backend.

### Mount-Time Synchronization

When `RecordingStateContext` mounts, it immediately executes `syncWithBackend()` to query the current Rust state:

```typescript
// RecordingStateContext.tsx
useEffect(() => {
  // Pull current state from Rust on mount (lines 23, 89-99)
  syncWithBackend();
}, []);

```

This `syncWithBackend` function invokes the Tauri command `get_recording_state`, returning the active status, duration, and pause flags to initialize the React context correctly.

### Real-Time Event Listeners

After hydration, the context registers listeners for the four lifecycle events: `recording-started`, `recording-stopped`, `recording-paused`, and `recording-resumed` (lines 40-80):

```typescript
// RecordingStateContext.tsx
const unlistenStarted = await recordingService.onRecordingStarted(() => {
  setState(prev => ({ 
    ...prev, 
    isRecording: true, 
    status: RecordingStatus.RECORDING 
  }));
  startPolling();
});

```

These callbacks update the internal `state` object and trigger the derived `status` helper, ensuring any state change initiated from the Rust side (or another UI component) immediately reflects in the sidebar.

## Polling for Active Recording State

While a recording is active, `RecordingStateContext` runs a 500ms polling loop to capture duration updates and pause toggles that might occur outside the discrete event stream:

```typescript
// RecordingStateContext.tsx (lines 15-17)
useEffect(() => {
  pollingIntervalRef.current = setInterval(syncWithBackend, 500);
  return () => clearInterval(pollingIntervalRef.current);
}, [state.isRecording]);

```

The `syncWithBackend` method queries Rust for the latest `durationSeconds` and `isPaused` values. This polling automatically stops when the recording ends, preventing unnecessary overhead during idle states.

## Integrating Transcript State

Transcript synchronization follows the same pattern through `TranscriptContext` (located in [`frontend/src/contexts/TranscriptContext.tsx`](https://github.com/Zackriya-Solutions/meetily/blob/main/frontend/src/contexts/TranscriptContext.tsx)). It subscribes to `recordingService` events like `transcript-update` and `api_search_transcripts`.

The sidebar consumes transcript capabilities via the `searchTranscripts` method (lines 66-84 of [`SidebarProvider.tsx`](https://github.com/Zackriya-Solutions/meetily/blob/main/SidebarProvider.tsx)), which only functions when the recording state allows. This coupling ensures transcript search is disabled until the Rust backend has processed audio segments.

## The Tauri Bridge: recordingService

All communication flows through [`frontend/src/services/recordingService.ts`](https://github.com/Zackriya-Solutions/meetily/blob/main/frontend/src/services/recordingService.ts), a thin wrapper around Tauri's `invoke` and `listen` APIs:

```typescript
// recordingService.ts
async getRecordingState(): Promise<RecordingState> {
  return invoke<RecordingState>('get_recording_state');
}

async onRecordingStarted(cb: () => void): Promise<UnlistenFn> {
  return listen('recording-started', cb);
}

async startRecording(): Promise<void> {
  return invoke('start_recording');
}

```

This service singleton prevents multiple components from creating redundant Tauri listeners. It marshals complex Rust structs into TypeScript interfaces that `RecordingStateContext` can consume directly.

## Summary

- **Initial sync**: `RecordingStateContext` calls `recordingService.getRecordingState()` on mount to prevent desync after refresh.
- **Event-driven updates**: Listeners for `recording-started`, `stopped`, `paused`, and `resumed` events push Rust state changes into React.
- **Active polling**: A 500ms interval updates duration and pause flags while recording is active.
- **Sidebar consumption**: `SidebarProvider` reads the `isRecording` flag to toggle between start/stop UI modes and handle navigation logic.
- **Transcript parity**: `TranscriptContext` subscribes to the same event source, keeping search availability coupled to recording status.

## Frequently Asked Questions

### How does Meetily prevent the sidebar from showing the wrong recording state after a page refresh?

The `RecordingStateContext` executes `syncWithBackend()` immediately upon mounting (lines 89-99 of [`RecordingStateContext.tsx`](https://github.com/Zackriya-Solutions/meetily/blob/main/RecordingStateContext.tsx)). This invokes the Rust command `get_recording_state` to hydrate React state with the actual backend status, fixing the "page-refresh desync" bug where the UI incorrectly assumed no recording was active.

### What is the polling interval used for in RecordingStateContext?

While recording is active, the context runs `setInterval(syncWithBackend, 500)` (lines 15-17) to continuously fetch duration updates and pause status from Rust. Discrete events handle state transitions, but polling captures incremental duration changes that occur between events.

### How does the sidebar trigger recording without directly calling Rust?

`SidebarProvider` uses the `handleRecordingToggle` function (lines 45-64) which checks the `isRecording` flag from context. If false, it either emits a custom `start-recording-from-sidebar` DOM event or sets `sessionStorage` flags and navigates to the home page. The actual Tauri invocation happens in the Home component, keeping the sidebar decoupled from direct backend calls.

### Can transcript search work while recording is paused?

Transcript search availability depends on `TranscriptContext`, which listens to the same `recordingService` events as `RecordingStateContext`. While the analysis shows transcript updates occur during `transcript-update` events, the sidebar's `searchTranscripts` functionality (lines 66-84) is typically gated by the recording state, ensuring search operations align with the Rust backend's current processing capabilities.