Skip to Content
DesktopForge - Development Guide

Forge - Development Guide

This guide covers setting up the development environment, understanding the codebase structure, and contributing to Forge.


Development Environment Setup

Prerequisites

ToolVersionInstallation
Node.js18+nodejs.org 
pnpm8+npm install -g pnpm
Rust1.70+rustup.rs 
Tauri CLI2.xcargo install tauri-cli

Platform-Specific Setup

macOS

# Install Xcode Command Line Tools xcode-select --install # Install Rust curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

Windows

  1. Install Visual Studio Build Tools 
  2. Install Rust 
  3. Ensure cargo is in your PATH

Linux

# Ubuntu/Debian sudo apt update sudo apt install build-essential libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf # Install Rust curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

Project Setup

Clone and Install

# Clone the repository git clone https://github.com/pktikkani/forge.git cd forge # Install frontend dependencies pnpm install

Running in Development

# Start development server with hot reload pnpm tauri:dev

This starts:

  • Vite dev server on http://localhost:1420
  • Tauri app connecting to the dev server
  • Hot module replacement for frontend changes
  • Auto-rebuild for Rust changes

Development Credentials

For development, you’ll need:

  1. Turso Database - Create a development database:

    turso db create forge-dev turso db show forge-dev --url turso db tokens create forge-dev
  2. Azure AD App - Register a dev app with redirect URI forge://oauth/callback

  3. LiveKit (optional) - Create a development project

Enter these in the app’s Settings page on first run.


Project Structure

forge/ ├── src/ # Frontend (React/TypeScript) │ ├── components/ # React components │ │ ├── ui/ # shadcn/ui primitives │ │ ├── dashboard/ # Dashboard-specific components │ │ ├── shared/ # Shared components │ │ └── calling/ # LiveKit voice/video │ ├── pages/ # Route pages │ ├── hooks/ # Custom React hooks │ ├── stores/ # Zustand state stores │ ├── lib/ # Utilities and API clients │ │ ├── tauri-api.ts # Tauri command wrappers │ │ ├── settings-store.ts # Persistent settings │ │ └── utils.ts # Helper functions │ ├── types/ # TypeScript definitions │ ├── App.tsx # App root with routing │ └── main.tsx # Entry point ├── src-tauri/ # Backend (Rust) │ ├── src/ │ │ ├── lib.rs # Plugin registration, setup │ │ ├── app_commands.rs # Window and init commands │ │ ├── commands.rs # Settings, OAuth, LiveKit │ │ ├── turso_commands.rs # CRUD operations │ │ ├── shared_commands.rs # Presence and calling │ │ ├── turso.rs # Database models and queries │ │ ├── db.rs # Local SQLite │ │ └── models.rs # Local data models │ ├── Cargo.toml # Rust dependencies │ └── tauri.conf.json # Tauri configuration ├── scripts/ # Build and utility scripts │ └── uninstall-macos.sh # macOS uninstaller ├── package.json # Node dependencies ├── vite.config.ts # Vite configuration ├── tailwind.config.js # Tailwind CSS config └── tsconfig.json # TypeScript config

Frontend Architecture

Technology Stack

LibraryPurpose
React 19UI framework
TypeScriptType safety
Tailwind CSSStyling
shadcn/uiComponent library
React QueryServer state management
ZustandClient state management
React RouterNavigation
Framer MotionAnimations

State Management

Zustand Stores (src/stores/):

  • auth-store.ts - User session and authentication
  • settings-store.ts - App settings state (UI)

React Query (src/hooks/):

  • Server state (Ideas, Goals, Tasks)
  • Automatic caching and refetching
  • Optimistic updates

Persistent Storage (src/lib/settings-store.ts):

  • Uses @tauri-apps/plugin-store
  • Survives app reinstalls
  • Stores Turso/LiveKit credentials

Component Patterns

// Example component structure import { useQuery, useMutation } from "@tanstack/react-query"; import { tursoApi } from "@/lib/tauri-api"; import { Button } from "@/components/ui/button"; export function IdeaList() { // Fetch data with React Query const { data: ideas, isLoading } = useQuery({ queryKey: ["ideas"], queryFn: tursoApi.getIdeas, }); // Mutation with optimistic update const createIdea = useMutation({ mutationFn: tursoApi.createIdea, onSuccess: () => queryClient.invalidateQueries(["ideas"]), }); if (isLoading) return <Spinner />; return ( <div> {ideas?.map((idea) => ( <IdeaCard key={idea.id} idea={idea} /> ))} </div> ); }

Tauri API Integration

Frontend communicates with Rust via the invoke function:

// src/lib/tauri-api.ts import { invoke } from "@tauri-apps/api/core"; export const tursoApi = { getIdeas: () => invoke<Idea[]>("get_all_ideas"), createIdea: (idea: CreateIdeaInput) => invoke<Idea>("create_idea", { idea }), updateIdea: (idea: UpdateIdeaInput) => invoke<Idea>("update_idea", { idea }), deleteIdea: (id: string) => invoke<void>("delete_idea", { id }), };

Backend Architecture

Tauri Commands

Commands are defined with the #[tauri::command] attribute:

// src-tauri/src/turso_commands.rs #[tauri::command] pub async fn get_all_ideas( turso_state: State<'_, TursoState>, ) -> Result<Vec<Idea>, String> { let guard = turso_state.0.read().await; let db = guard.as_ref().ok_or("Turso not initialized")?; db.get_all_ideas().await.map_err(|e| e.to_string()) }

Commands are registered in lib.rs:

.invoke_handler(tauri::generate_handler![ show_main_window, initialize_turso, get_all_ideas, create_idea, // ... more commands ])

Database Operations

Turso queries use libSQL (SQLite-compatible):

// src-tauri/src/turso.rs impl TursoDatabase { pub async fn get_all_ideas(&self) -> Result<Vec<Idea>> { let mut rows = self.conn.query( "SELECT id, title, description, status, priority, author_id, created_at, updated_at FROM ideas WHERE author_id IN (SELECT id FROM users WHERE tenant_id = ?)", [&self.tenant_id], ).await?; let mut ideas = Vec::new(); while let Some(row) = rows.next().await? { ideas.push(Idea::from_row(&row)?); } Ok(ideas) } }

State Management

Rust state is managed through Tauri’s state system:

// Lazy-initialized Turso connection pub struct TursoState(pub Arc<RwLock<Option<TursoDatabase>>>); // Access in commands #[tauri::command] pub async fn some_command( turso_state: State<'_, TursoState>, ) -> Result<(), String> { let guard = turso_state.0.read().await; let db = guard.as_ref().ok_or("Not initialized")?; // Use db... }

Adding New Features

Adding a New Entity

  1. Define the Rust model (src-tauri/src/turso.rs):

    #[derive(Debug, Serialize, Deserialize)] pub struct NewEntity { pub id: String, pub title: String, // ... fields }
  2. Add database operations:

    impl TursoDatabase { pub async fn create_new_entity(&self, entity: NewEntity) -> Result<NewEntity> { self.conn.execute( "INSERT INTO new_entities (id, title, ...) VALUES (?, ?, ...)", params![entity.id, entity.title, ...], ).await?; Ok(entity) } }
  3. Create Tauri commands (src-tauri/src/turso_commands.rs):

    #[tauri::command] pub async fn create_new_entity( turso_state: State<'_, TursoState>, entity: NewEntity, ) -> Result<NewEntity, String> { // Implementation }
  4. Register commands in lib.rs

  5. Add TypeScript types (src/types/):

    export interface NewEntity { id: string; title: string; // ... fields }
  6. Add API wrapper (src/lib/tauri-api.ts):

    export const newEntityApi = { create: (entity: NewEntity) => invoke<NewEntity>("create_new_entity", { entity }), };
  7. Create React components and hooks

Adding a New Page

  1. Create the page component (src/pages/NewPage.tsx)

  2. Add route in App.tsx:

    <Route path="/new-page" element={<NewPage />} />
  3. Add navigation in the sidebar component


Testing

Frontend Testing

# Run Jest tests pnpm test # Run with coverage pnpm test:coverage

Rust Testing

# Run Rust tests cd src-tauri cargo test # Run specific test cargo test test_name

E2E Testing

# Run Playwright tests pnpm test:e2e

Code Quality

Linting

# Lint frontend pnpm lint # Lint Rust cd src-tauri cargo clippy

Formatting

# Format frontend pnpm format # Format Rust cd src-tauri cargo fmt

Type Checking

# TypeScript type check pnpm typecheck # Rust type check cd src-tauri cargo check

Debugging

Frontend Debugging

  1. Open DevTools in the app: Cmd+Option+I (macOS) or Ctrl+Shift+I (Windows/Linux)
  2. Use React DevTools extension
  3. Check Console for errors
  4. Use Network tab for API calls

Rust Debugging

  1. Logging:

    use log::{info, debug, error}; info!("Processing request: {:?}", request);
  2. View logs:

    • macOS: ~/Library/Logs/com.forge.desktop/
    • Windows: %APPDATA%\com.forge.desktop\logs\
    • Linux: ~/.config/com.forge.desktop/logs/
  3. Debug build:

    RUST_BACKTRACE=1 pnpm tauri:dev

Database Debugging

# Connect to Turso database turso db shell forge-dev # Query tables .tables SELECT * FROM ideas LIMIT 5;

Common Development Tasks

Adding a New Tauri Plugin

  1. Add to Cargo.toml:

    [dependencies] tauri-plugin-new = "2"
  2. Register in lib.rs:

    .plugin(tauri_plugin_new::init())
  3. Add to capabilities/default.json if needed

  4. Use in frontend:

    import { someFunction } from "@tauri-apps/plugin-new";

Adding a shadcn/ui Component

# Add a new component pnpm dlx shadcn@latest add button pnpm dlx shadcn@latest add dialog

Components are added to src/components/ui/.

Updating Dependencies

# Update frontend dependencies pnpm update # Update Rust dependencies cd src-tauri cargo update

Git Workflow

Branch Naming

  • feature/description - New features
  • fix/description - Bug fixes
  • refactor/description - Code refactoring
  • docs/description - Documentation updates

Commit Messages

Follow conventional commits:

feat: add user presence indicator fix: resolve task drag-drop issue docs: update deployment guide refactor: simplify auth flow

Pull Request Process

  1. Create a feature branch
  2. Make changes and test locally
  3. Push branch and create PR
  4. Request review
  5. Address feedback
  6. Merge after approval

Troubleshooting Development

Build Errors

“Cannot find module” errors:

pnpm install

Rust compilation errors:

cd src-tauri cargo clean cargo build

Tauri command not found:

cargo install tauri-cli

Runtime Errors

“Turso not initialized”:

  • Enter credentials in Settings page
  • Check console for connection errors

“Failed to invoke command”:

  • Verify command is registered in lib.rs
  • Check command name matches exactly
  • Verify parameter types match

Hot Reload Not Working

Frontend changes not reflecting:

  • Check Vite dev server is running
  • Clear browser cache
  • Restart dev server

Rust changes not reflecting:

  • Rust auto-rebuilds, but may need manual restart
  • Run cargo build in src-tauri/

Resources

Documentation

Community

Source Code

Last updated on