- Moved all hookify configuration files from `.claude/` to `.claude/hooks/` subdirectory for better organization - Added four new blocking hooks to prevent common error handling anti-patterns: - `block-broad-exception-handler`: Prevents catching generic `Exception` with only logging - `block-datetime-now-fallback`: Blocks returning `datetime.now()` as fallback on parse failures to prevent data corruption - `block-default
16 KiB
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
# 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
# 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
# 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
NoteFlowAdapterinterface allows swapping implementations TauriAdapterusesinvoke()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.rsviaapp_invoke_handler!macro - State is managed through
AppStatewith thread-safeArcwrappers - gRPC calls use
tonicclient; streaming handled byStreamManager - Audio capture uses
cpal, playback usesrodio
TypeScript ↔ Rust Bridge
The TauriAdapter calls Rust commands via Tauri's invoke():
// TypeScript (src/api/tauri-adapter.ts)
const result = await invoke<MeetingResult>('create_meeting', { request });
// Rust (src-tauri/src/commands/meeting.rs)
#[tauri::command]
pub async fn create_meeting(
state: State<'_, AppState>,
request: CreateMeetingRequest,
) -> Result<Meeting, String> { ... }
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
-
Search existing modules first:
src/lib/— Utilities, helpers, formatterssrc/hooks/— React hooks (don't recreate existing hooks)src/api/— API utilities and typessrc-tauri/src/commands/— Rust command utilitiessrc-tauri/src/grpc/— gRPC client utilities
-
Use symbolic search:
# Find existing functions by name pattern grep -r "function_name" src/ cargo grep "fn function_name" src-tauri/ -
Check imports in similar files — they reveal available utilities
-
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:
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— NoanytypesnoNonNullAssertion: error— No!assertionsnoUnusedImports: error,noUnusedVariables: erroruseConst: error,useImportType: error
Type Safety:
- Strict TypeScript mode enabled
- No
@ts-ignoreor@ts-nocheckcomments - No
as anyoras unknownassertions
Rust
Clippy Configuration (src-tauri/clippy.toml):
cognitive-complexity-threshold: 25too-many-lines-threshold: 100too-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:
// 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:
clientlogpersists 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=truein 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
anytypes 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
- Rust: Add command in
src-tauri/src/commands/*.rs - Rust: Register in
src-tauri/src/lib.rs→app_invoke_handler! - TypeScript: Add to
src/api/tauri-adapter.ts - TypeScript: Add to
src/api/interface.ts(if new method) - TypeScript: Add types to
src/api/types/
Adding a New API Method
- Update
interface.tswith method signature - Implement in
tauri-adapter.ts(production) - Implement in
mock-adapter.ts(development/testing) - Add cached version in
cached/*.tsif caching needed
gRPC Schema Changes
When the backend proto changes:
- Rebuild Tauri:
npm run tauri:build(triggersbuild.rsto regeneratenoteflow.rs) - Update Rust types in
src-tauri/src/grpc/types/ - Update TypeScript types in
src/api/types/ - Update adapters as needed
Testing Patterns
- Tests use Vitest with jsdom environment
@testing-library/reactfor component testing- Tauri plugins are mocked in
src/test/mocks/ src/test/setup.tsconfigures jest-dom matchers
# 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 |