-
-
Notifications
You must be signed in to change notification settings - Fork 0
Development Guide
Kenneth LaCroix edited this page Apr 13, 2026
·
1 revision
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 devFor platform build prerequisites, see Building from Source.
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 testsrc/ # 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
- All
#[tauri::command]functions must be registered insrc-tauri/src/lib.rs - New command modules → add to
src-tauri/src/commands/mod.rsANDlib.rs - Never hold the DB mutex across
awaitor HTTP calls — get value, drop lock, then proceed -
rusqliteis non-reentrant: never call a DB function from within a locked DB function - Settings table created lazily via
ensure_settings_table()in each command
- Define in
src-tauri/src/commands/<module>.rs:
#[tauri::command]
pub async fn my_command(
db: tauri::State<'_, Database>,
param: String,
) -> Result<MyResponse, String> {
// ...
}- Declare in
src-tauri/src/commands/mod.rs:
pub mod my_module;-
Register in
src-tauri/src/lib.rsinsideinvoke_handler!(...). -
Add to
src-tauri/capabilities/default.json:
{ "identifier": "core:default:allow-my-command" }- 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 });
}- Tauri IPC wrappers live in
src/lib/*.ts; hooks live insrc/hooks/ - Settings:
settings.json(frontend Zustand) + SQLitesettingstable (set_setting) -
tokio::join!requires explicit tokio dep — prefer sequential.awaitinstead - In-flight feature plans in
active-plans/(git-tracked); completed plans archived indocs/internal/plans/(gitignored)
- 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
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
- TypeScript strict mode — no
anytypes - 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