Skip to content

Development Guide

Kenneth LaCroix edited this page Apr 13, 2026 · 1 revision

Development Guide

Quick Start

git clone https://github.com/kenlacroix/moodhaven-journal.git
cd moodhaven-journal
npm install

npm run tauri dev          # desktop dev with hot reload
npm run dev:web            # browser dev server (no Rust needed)
VITE_DEV_MODE=bypass npm run dev:web   # skip auth gate for browser dev

For platform build prerequisites, see Building from Source.


Commands

npm run tauri dev          # desktop dev
npm run tauri build        # production build
npm run dev:web            # browser dev server
npm run build:web          # browser build → dist-web/
npm test                   # run all tests (693 currently)
npm run test:watch         # watch mode
npm run test:coverage      # v8 coverage report
npm run typecheck          # tsc --noEmit
npm run lint               # ESLint
cd src-tauri && cargo check
cd src-tauri && cargo test

Architecture Overview

src/                        # React frontend
├── App.tsx                 # Root: routing, lock screen, first-run wizard
├── components/             # UI components by feature
├── features/               # Full page views (writing, timeline, calendar…)
├── hooks/                  # Business logic (useJournal, useAnalytics…)
├── stores/                 # Zustand stores (app, settings, books, peerSync)
├── lib/                    # Service modules + utilities
│   ├── services/           # IPC wrappers, crypto, sync, AI, STT…
│   └── backend/            # Browser-mode IndexedDB backend
└── types/                  # TypeScript type definitions

src-tauri/                  # Rust backend
├── src/
│   ├── lib.rs              # Tauri builder + all ~109 command registrations
│   ├── commands/           # ~21 command modules
│   └── db/mod.rs           # SQLite schema, migrations, Database struct
└── capabilities/default.json  # Tauri ACL

Non-Obvious Conventions

Rust Commands

  • All #[tauri::command] functions must be registered in src-tauri/src/lib.rs
  • New command modules → add to src-tauri/src/commands/mod.rs AND lib.rs
  • Never hold the DB mutex across await or HTTP calls — get value, drop lock, then proceed
  • rusqlite is non-reentrant: never call a DB function from within a locked DB function
  • Settings table created lazily via ensure_settings_table() in each command

Adding a New Tauri Command

  1. Define in src-tauri/src/commands/<module>.rs:
#[tauri::command]
pub async fn my_command(
    db: tauri::State<'_, Database>,
    param: String,
) -> Result<MyResponse, String> {
    // ...
}
  1. Declare in src-tauri/src/commands/mod.rs:
pub mod my_module;
  1. Register in src-tauri/src/lib.rs inside invoke_handler!(...).

  2. Add to src-tauri/capabilities/default.json:

{ "identifier": "core:default:allow-my-command" }
  1. Add IPC wrapper in src/lib/myService.ts:
import { invoke } from '@tauri-apps/api/core';
export async function myCommand(param: string): Promise<MyResponse> {
  return invoke('my_command', { param });
}

Frontend

  • Tauri IPC wrappers live in src/lib/*.ts; hooks live in src/hooks/
  • Settings: settings.json (frontend Zustand) + SQLite settings table (set_setting)
  • tokio::join! requires explicit tokio dep — prefer sequential .await instead
  • In-flight feature plans in active-plans/ (git-tracked); completed plans archived in docs/internal/plans/ (gitignored)

Security Rules (Non-Negotiable)

  • Journal text content NEVER sent to external APIs — metadata only
  • All encryption/decryption client-side; backend never sees plaintext
  • No master keys, admin passwords, or cloud recovery mechanisms
  • New commands that touch sensitive data must call require_unlocked() first

Testing

Tests run in jsdom via Vitest. Global Tauri IPC mock in src/test/setup.ts:

vi.mock('@tauri-apps/api/core', () => ({ invoke: vi.fn() }));

Per-test usage:

import { invoke } from '@tauri-apps/api/core';
const mockInvoke = vi.mocked(invoke);
beforeEach(() => vi.clearAllMocks());
mockInvoke.mockResolvedValue({ ok: true });

WebCrypto: jsdom doesn't fully support it. Use // @vitest-environment node at the top of crypto test files.

Test categories:

Category Approach
Pure utilities No mocking — test the function directly
Zustand stores Mock service modules, test via getState() / setState()
React components Testing Library queries + userEvent
Crypto // @vitest-environment node

Full testing guide: .claude/docs/testing.md


Code Style

  • TypeScript strict mode — no any types
  • Conventional commits: feat:, fix:, chore:, docs:
  • Max 400 lines changed per PR
  • No comments unless logic is non-obvious
  • Validate only at system boundaries (user input, external APIs)

Full conventions: CLAUDE.md

Clone this wiki locally