# CLAUDE.md - Client Development Reference This file provides guidance for working with the Tauri + React client code. For Python backend development, see `src/noteflow/CLAUDE.md`. ## Project Overview NoteFlow Client is a **Tauri + React desktop application** for intelligent meeting note-taking. It communicates with a Python gRPC backend for audio streaming, transcription, speaker diarization, and AI-powered summarization. The client consists of: - **React/TypeScript frontend** (`src/`) — UI components, hooks, contexts, and API layer - **Rust/Tauri backend** (`src-tauri/`) — Native IPC commands, gRPC client, audio capture/playback, encryption --- ## Development Commands ```bash # Install dependencies npm install # Development (web only) npm run dev # Desktop development (requires Rust toolchain) npm run tauri:dev # Build npm run build npm run tauri:build ``` ### Testing ```bash # Unit tests (Vitest) npm run test # Run once npm run test:watch # Watch mode # Run specific test file npx vitest run src/hooks/use-audio-devices.test.ts # Rust tests npm run test:rs # Equivalent: cd src-tauri && cargo test # E2E tests (Playwright) npm run test:e2e # Native E2E tests (WebdriverIO) npm run test:native # All tests npm run test:all ``` ### Quality Checks ```bash # TypeScript type checking npm run type-check # Linting (outputs to ../.hygeine/) npm run lint # Biome + ESLint npm run lint:fix # Auto-fix # Formatting npm run format # Biome format npm run format:check # Check only # Quality tests (code quality enforcement) npm run test:quality # Rust code quality npm run quality:rs ``` --- ## Architecture ### TypeScript Layer (`client/src/`) ``` src/ ├── api/ # API layer │ ├── tauri-adapter.ts # Main Tauri IPC adapter │ ├── mock-adapter.ts # Mock adapter for testing │ ├── cached-adapter.ts # Caching layer │ ├── connection-state.ts # Connection state machine │ ├── reconnection.ts # Auto-reconnection logic │ ├── interface.ts # Adapter interface │ ├── transcription-stream.ts │ ├── types/ # Type definitions │ │ ├── core.ts # Core types (Meeting, Segment, etc.) │ │ ├── enums.ts # Enum definitions │ │ ├── errors.ts # Error types │ │ ├── projects.ts # Project types │ │ ├── diagnostics.ts │ │ ├── requests.ts │ │ ├── features/ # Feature-specific types │ │ │ ├── webhooks.ts │ │ │ ├── calendar.ts │ │ │ ├── ner.ts │ │ │ ├── sync.ts │ │ │ ├── identity.ts │ │ │ ├── oidc.ts │ │ │ └── observability.ts │ │ └── requests/ # Request types by domain │ └── cached/ # Cached adapter implementations │ ├── base.ts │ ├── meetings.ts │ ├── projects.ts │ ├── diarization.ts │ ├── annotations.ts │ ├── templates.ts │ ├── webhooks.ts │ ├── calendar.ts │ ├── entities.ts │ ├── preferences.ts │ ├── observability.ts │ ├── triggers.ts │ ├── audio.ts │ ├── playback.ts │ ├── apps.ts │ └── readonly.ts ├── hooks/ # Custom React hooks │ ├── use-diarization.ts │ ├── use-cloud-consent.ts │ ├── use-webhooks.ts │ ├── use-oauth-flow.ts │ ├── use-calendar-sync.ts │ ├── use-entity-extraction.ts │ ├── use-audio-devices.ts │ ├── use-project.ts │ ├── use-project-members.ts │ ├── use-oidc-providers.ts │ ├── use-auth-flow.ts │ ├── use-integration-sync.ts │ ├── use-integration-validation.ts │ ├── use-secure-integration-secrets.ts │ ├── use-guarded-mutation.ts │ ├── use-panel-preferences.ts │ ├── use-preferences-sync.ts │ ├── use-meeting-reminders.ts │ ├── use-recording-app-policy.ts │ ├── use-post-processing.ts │ ├── use-toast.ts │ ├── use-mobile.tsx │ └── post-processing/ ├── contexts/ # React contexts │ ├── connection-context.tsx # gRPC connection context │ ├── connection-state.ts │ ├── workspace-context.tsx # Workspace context │ ├── workspace-state.ts │ ├── project-context.tsx # Project context │ └── project-state.ts ├── components/ # React components │ ├── ui/ # Reusable UI components (shadcn/ui) │ ├── recording/ # Recording-specific components │ ├── settings/ # Settings panel components │ ├── analytics/ # Analytics visualizations │ ├── projects/ # Project components │ ├── icons/ # Icon components │ └── ... # Top-level components ├── pages/ # Route pages │ ├── Home.tsx │ ├── Meetings.tsx │ ├── MeetingDetail.tsx │ ├── Recording.tsx │ ├── Projects.tsx │ ├── ProjectSettings.tsx │ ├── Settings.tsx │ ├── settings/ # Settings sub-pages │ ├── People.tsx │ ├── Tasks.tsx │ ├── Analytics.tsx │ ├── Index.tsx │ └── NotFound.tsx ├── lib/ # Utilities │ ├── config/ # Configuration (server, defaults, app-config, provider-endpoints) │ ├── cache/ # Client-side caching (meeting-cache) │ ├── format.ts │ ├── utils.ts │ ├── time.ts │ ├── crypto.ts │ ├── cva.ts │ ├── styles.ts │ ├── tauri-events.ts │ ├── preferences.ts │ ├── preferences-sync.ts │ ├── entity-store.ts │ ├── speaker-utils.ts │ ├── ai-providers.ts │ ├── ai-models.ts │ ├── integration-utils.ts │ ├── default-integrations.ts │ ├── status-constants.ts │ ├── timing-constants.ts │ ├── object-utils.ts │ ├── error-reporting.ts │ ├── client-logs.ts │ ├── client-log-events.ts │ ├── log-groups.ts │ ├── log-converters.ts │ ├── log-messages.ts │ ├── log-summarizer.ts │ └── log-group-summarizer.ts ├── types/ # Shared TypeScript types └── test/ # Test utilities ``` **Key Patterns:** - API abstraction via `NoteFlowAdapter` interface allows swapping implementations - `TauriAdapter` uses `invoke()` to call Rust commands which handle gRPC - React Query (`@tanstack/react-query`) for server state management - Contexts for global state: `ConnectionContext`, `WorkspaceContext`, `ProjectContext` ### Rust Layer (`client/src-tauri/src/`) ``` src-tauri/src/ ├── commands/ # Tauri IPC command handlers │ ├── recording/ # capture.rs, device.rs, audio.rs, app_policy.rs │ ├── playback/ # audio.rs, events.rs, tick.rs │ ├── triggers/ # audio.rs, polling.rs │ ├── meeting.rs │ ├── diarization.rs │ ├── annotation.rs │ ├── export.rs │ ├── summary.rs │ ├── entities.rs │ ├── calendar.rs │ ├── webhooks.rs │ ├── preferences.rs │ ├── observability.rs │ ├── sync.rs │ ├── projects.rs │ ├── identity.rs │ ├── oidc.rs │ ├── connection.rs │ ├── audio.rs │ ├── audio_testing.rs │ ├── apps.rs │ ├── apps_platform.rs │ ├── diagnostics.rs │ ├── shell.rs │ └── testing.rs ├── grpc/ # gRPC client │ ├── client/ # Client implementations by domain │ │ ├── core.rs │ │ ├── meetings.rs │ │ ├── annotations.rs │ │ ├── diarization.rs │ │ ├── identity.rs │ │ ├── projects.rs │ │ ├── preferences.rs │ │ ├── calendar.rs │ │ ├── webhooks.rs │ │ ├── observability.rs │ │ ├── oidc.rs │ │ ├── sync.rs │ │ └── converters.rs │ ├── types/ # Rust type definitions │ │ ├── core.rs │ │ ├── enums.rs │ │ ├── identity.rs │ │ ├── projects.rs │ │ ├── preferences.rs │ │ ├── calendar.rs │ │ ├── webhooks.rs │ │ ├── observability.rs │ │ ├── oidc.rs │ │ ├── sync.rs │ │ └── results.rs │ ├── streaming/ # Streaming converters │ └── noteflow.rs # Generated protobuf types ├── state/ # Runtime state management │ ├── app_state.rs │ ├── preferences.rs │ ├── playback.rs │ └── types.rs ├── audio/ # Audio capture and playback ├── cache/ # Memory caching ├── crypto/ # Cryptographic operations ├── events/ # Tauri event emission ├── triggers/ # Trigger detection ├── error/ # Error types ├── identity/ # Identity management ├── config.rs # Configuration ├── constants.rs # Constants ├── helpers.rs # Helper functions ├── oauth_loopback.rs # OAuth callback server ├── main.rs # Application entry point └── lib.rs # Library exports ``` **Key Patterns:** - Commands are registered in `lib.rs` via `app_invoke_handler!` macro - State is managed through `AppState` with thread-safe `Arc` wrappers - gRPC calls use `tonic` client; streaming handled by `StreamManager` - Audio capture uses `cpal`, playback uses `rodio` ### TypeScript ↔ Rust Bridge The `TauriAdapter` calls Rust commands via Tauri's `invoke()`: ```typescript // TypeScript (src/api/tauri-adapter.ts) const result = await invoke('create_meeting', { request }); // Rust (src-tauri/src/commands/meeting.rs) #[tauri::command] pub async fn create_meeting( state: State<'_, AppState>, request: CreateMeetingRequest, ) -> Result { ... } ``` --- ## Code Reuse (CRITICAL) **BEFORE writing ANY new code, you MUST search for existing implementations.** This is not optional. Redundant code creates maintenance burden, inconsistency, and bugs. ### Mandatory Search Process 1. **Search existing modules first:** - `src/lib/` — Utilities, helpers, formatters - `src/hooks/` — React hooks (don't recreate existing hooks) - `src/api/` — API utilities and types - `src-tauri/src/commands/` — Rust command utilities - `src-tauri/src/grpc/` — gRPC client utilities 2. **Use symbolic search:** ```bash # Find existing functions by name pattern grep -r "function_name" src/ cargo grep "fn function_name" src-tauri/ ``` 3. **Check imports in similar files** — they reveal available utilities 4. **Only create new code if:** - No existing implementation exists - Existing code cannot be reasonably extended - You have explicit approval for new abstractions ### Anti-Patterns (FORBIDDEN) | Anti-Pattern | Correct Approach | |--------------|------------------| | New wrapper around existing function | Use existing function directly | | Duplicate utility in different module | Import from canonical location | | "Quick" helper that duplicates logic | Find and reuse existing helper | | New hook when existing hook suffices | Extend or compose existing hooks | ### Examples **BAD:** Creating `query_capture_config()` when `resolve_input_device()` + `select_input_config()` already exist **GOOD:** Using existing functions directly: ```rust use device::{resolve_input_device, select_input_config}; let device = resolve_input_device(device_id)?; let config = select_input_config(&device, rate, channels)?; ``` **BAD:** Writing new formatting helpers in a component **GOOD:** Checking `src/lib/format.ts` first and adding there if truly needed --- ## Code Quality Standards ### TypeScript **Linting:** Biome with strict rules - `noExplicitAny: error` — No `any` types - `noNonNullAssertion: error` — No `!` assertions - `noUnusedImports: error`, `noUnusedVariables: error` - `useConst: error`, `useImportType: error` **Type Safety:** - Strict TypeScript mode enabled - No `@ts-ignore` or `@ts-nocheck` comments - No `as any` or `as unknown` assertions ### Rust **Clippy Configuration** (`src-tauri/clippy.toml`): - `cognitive-complexity-threshold: 25` - `too-many-lines-threshold: 100` - `too-many-arguments-threshold: 7` **Quality Script** (`npm run quality:rs`): - Magic number detection - Long function detection (>90 lines) - Deep nesting detection (>7 levels) - `unwrap()` usage detection - Module size limits (>500 lines flagged) ### Client Logging (CRITICAL) **NEVER use `console.log`, `console.error`, `console.warn`, or `console.debug` directly.** Always use the `clientlog` system via `src/lib/debug.ts` or `src/lib/client-logs.ts`: ```typescript // For debug logging (controlled by DEBUG flag) import { debug } from '@/lib/debug'; const log = debug('MyComponent'); log('Something happened', { detail: 'value' }); // For error logging (always outputs) import { errorLog } from '@/lib/debug'; const logError = errorLog('MyComponent'); logError('Something failed', error); // For direct clientlog access import { addClientLog } from '@/lib/client-logs'; addClientLog({ level: 'info', source: 'app', message: 'Event occurred', details: 'Additional context', }); ``` **Why:** - `clientlog` persists logs to localStorage for later viewing in Analytics - Logs are structured with level, source, timestamp, and metadata - Debug logs can be toggled at runtime via `DEBUG=true` in localStorage - Console logging is ephemeral and not accessible to users **Log Levels:** `debug` | `info` | `warning` | `error` **Log Sources:** `app` | `api` | `sync` | `auth` | `system` ### Test Quality Enforcement The `src/test/code-quality.test.ts` suite enforces: - No repeated string literals across files - No duplicate utility implementations - No TODO/FIXME comments - No commented-out code - No magic numbers - No hardcoded colors or API endpoints - No `any` types or type assertions - File size limits (500 lines max, with exceptions) - Centralized helpers (format/parse/convert utilities) --- ## Key Integration Points ### Adding a New Tauri Command 1. **Rust**: Add command in `src-tauri/src/commands/*.rs` 2. **Rust**: Register in `src-tauri/src/lib.rs` → `app_invoke_handler!` 3. **TypeScript**: Add to `src/api/tauri-adapter.ts` 4. **TypeScript**: Add to `src/api/interface.ts` (if new method) 5. **TypeScript**: Add types to `src/api/types/` ### Adding a New API Method 1. Update `interface.ts` with method signature 2. Implement in `tauri-adapter.ts` (production) 3. Implement in `mock-adapter.ts` (development/testing) 4. Add cached version in `cached/*.ts` if caching needed ### gRPC Schema Changes When the backend proto changes: 1. Rebuild Tauri: `npm run tauri:build` (triggers `build.rs` to regenerate `noteflow.rs`) 2. Update Rust types in `src-tauri/src/grpc/types/` 3. Update TypeScript types in `src/api/types/` 4. Update adapters as needed --- ## Testing Patterns - Tests use Vitest with jsdom environment - `@testing-library/react` for component testing - Tauri plugins are mocked in `src/test/mocks/` - `src/test/setup.ts` configures jest-dom matchers ```bash # Run single test file npx vitest run src/hooks/use-audio-devices.test.ts # Run tests matching pattern npx vitest run -t "should handle" # Run with coverage npx vitest run --coverage ``` --- ## Configuration Files | File | Purpose | |------|---------| | `biome.json` | Linting and formatting rules | | `tsconfig.json` | TypeScript configuration | | `vitest.config.ts` | Test runner configuration | | `src-tauri/Cargo.toml` | Rust dependencies | | `src-tauri/clippy.toml` | Rust linting thresholds | | `src-tauri/tauri.conf.json` | Tauri app configuration |