Jean Documentation

repository·main·Indexed 22 days ago

https://github.com/coollabsio/jean

A native desktop AI assistant for managing multiple projects, git worktrees, and chat sessions. Jean provides a unified interface for AI CLIs like Claude and Cursor, integrates with GitHub and Linear, and includes a headless server (jean-server) for Linux environments. The documentation covers installation via Homebrew, web access configuration, secure networking via Tailscale, and developer guidelines for extending its command system and contributing via Rust and TypeScript.

Tokens
86.6K
Snippets
237
Records
372
Agent score
77%

What's inside Jean

  1. How Command Context performance is optimized

    main

    The CommandContext is designed to prevent render cascades and performance issues through several mechanisms:

    • Stable References: Uses useMemo to ensure the context object remains stable.
    • Direct State Access: Commands access store state via getState() rather than using subscriptions, which avoids unnecessary re-renders.
    • Decoupling: Uses event-driven patterns (like dispatchEvent) to prevent tight coupling between the command system and other modules.
  2. How the "Onion" State Management Pattern works

    main

    Jean uses a three-layer state hierarchy to ensure predictable data flow and prevent state chaos. Developers should organize state according to these layers:

    1. useState (Component UI State): Local, ephemeral state used only within a single component.
    2. Zustand (Global UI State): Shared state used across multiple components for UI orchestration.
    3. TanStack Query (Persistent Data): Server-state or persistent data fetched from external sources or the filesystem.

    Following this hierarchy ensures that data flows predictably from persistent storage up to the local UI components.

    ┌─────────────────────────────────────┐
    │           useState                  │  ← Component UI State
    │  ┌─────────────────────────────────┐│
    │  │          Zustand                ││  ← Global UI State
    │  │  ┌─────────────────────────────┐││
    │  │  │      TanStack Query         │││  ← Persistent Data
    │  │  └─────────────────────────────┘││
    │  └─────────────────────────────────┘│
    └─────────────────────────────────────┘
  3. Communicate between Rust and React using the Event-Driven Bridge

    main

    Jean uses an event-driven bridge to maintain loose coupling between the Rust backend and the React frontend.

    Rust → React

    Emit events from Rust to be listened to in the frontend using app.emit.

    React → Rust

    Invoke Rust commands from the frontend using the invoke function, which returns a Result for error handling.

    // Rust: Menu click emits event
    app.on_menu_event(|app, event| {
        let _ = app.emit("menu-preferences", ());
    });
    
    // React: Command invocation with error handling
    const result = await invoke<Result>('my_command', { args })
  4. How the Embedded Browser Grab Bridge works

    main

    Because Jean's embedded browser uses native Tauri child Webviews, the main React application cannot directly inspect the DOM inside those Webviews. To enable DOM selection, Jean uses an injected, local React Grab bundle.

    The runtime flow is as follows:

    1. The Grab DOM element button in the Browser toolbar triggers browser_enable_grab(tabId).
    2. The Rust backend identifies the child Webview for the given tabId and injects src-tauri/src/browser/react_grab.global.js using webview.eval(...).
    3. The injected wrapper initializes React Grab (with telemetry disabled), registers the Send to Jean Chat action, and activates Grab in toggle mode.
    4. When an element is selected or copied, the child Webview invokes browser_report_grab_context.
    5. Rust validates and truncates the payload, then emits the browser:grab-context event.
    6. The useBrowserEvents() hook in the React app formats this payload and dispatches append-chat-input, which inserts the selected context into the active chat draft.
  5. TypeScript and React coding standards

    main

    When writing TypeScript and React code, adhere to these patterns:

    • Use TypeScript for all new code.
    • Use functional components with hooks.
    • Prefer composition over inheritance.
    • Use meaningful variable and function names.

    Example of good practice:

    // ✅ Good
    const UserProfile = ({ userId }: { userId: string }) => {
      const { data: user, isLoading } = useUser(userId)
    
      if (isLoading) return <LoadingSpinner />
      return <div>{user?.name}</div>
    }
  6. How the Model Catalog works in Jean

    main

    Jean manages model metadata (the Model Catalog) to define available models and their capabilities. It uses a tiered loading strategy:

    1. Remote Metadata: Jean first attempts to load model metadata from the coolLabs CDN (coollabs-cdn/json/jean/models.json).
    2. Bundled Fallback: If the network or cache is unavailable, Jean falls back to metadata bundled in src/services/model-catalog.ts.

    Merging Logic:

    • Claude and Codex: Remote entries completely replace the bundled model list.
    • Other Backends: Remote entries are merged with models discovered via the CLI.
    • Fast Variants: These models automatically inherit the reasoning capabilities of their base model.
  7. How the Native Menu System works

    main

    Jean uses a cross-platform native menu system that integrates with keyboard shortcuts and the command system. The architecture follows a three-tier pattern to connect native OS menus to React-based application logic:

    1. Rust Menu Definition: Menus are constructed in the Tauri backend using SubmenuBuilder, MenuItemBuilder, and PredefinedMenuItem. Each custom item is assigned a unique ID.
    2. Event Handling Pattern: When a menu item is clicked, the Rust backend catches the event via app.on_menu_event. The backend then emits a specific event to the frontend using app.emit("menu-{id}", ()).
    3. React Event Listeners: The frontend listens for these emitted events using the listen function (from Tauri's API) to trigger application logic, such as opening dialogs or updating UI state.

    Note: For items with user-configurable shortcuts (like View or Git menus), accelerators are intentionally omitted from the menu definition to avoid displaying stale or incorrect keybindings.

    // 1. Define in Rust
    let app_submenu = SubmenuBuilder::new(app, "Jean")
        .item(&MenuItemBuilder::with_id("about", "About Jean").build(app)?)
        .build()?;
    
    // 2. Emit event in Rust
    app.on_menu_event(move |app, event| {
        match event.id().as_ref() {
            "about" => { let _ = app.emit("menu-about", ()); }
            _ => {}
        }
    });
    
    // 3. Listen in React
    listen('menu-about', async () => {
      // App logic here
    });
  8. Understand remote connection limitations and capabilities

    main

    When using a remote Jean Web Access server instead of a Local instance, the following architectural constraints apply:

    Capabilities

    • Transport: Commands and events are routed via authenticated HTTP/WebSocket using the selected absolute base URL.
    • Bootstrap: Remote HTTP bootstrap, file URLs, and WebSocket URLs all use the selected base URL.
    • CORS: The remote server must allow standard Tauri desktop origins via CORS.

    Limitations

    • Desktop Operations: Native window and clipboard capabilities remain local to the desktop client. Backend-side desktop operations (operations that require access to the server's local OS) are unavailable while a remote connection is active.
    • State: Switching connections reloads the frontend, providing fresh TanStack Query and Zustand state to prevent stale events from crossing instance boundaries.
  9. Project file organization

    main

    The project uses the following directory structure:

    src/
    ├── components/          # Reusable UI components
    │   ├── ui/             # Base UI components (shadcn/ui)
    │   └── feature/         # Feature-specific components
    ├── hooks/              # Custom React hooks
    ├── lib/               # Utility functions and configurations
    ├── services/           # External API integrations
    ├── store/             # Zustand stores
    └── types/             # TypeScript type definitions
    src/
    ├── components/
    │   ├── ui/
    │   └── feature/
    ├── hooks/
    ├── lib/
    ├── services/
    ├── store/
    └── types/
  10. How file system organization works for data

    main

    Data is organized within the application's standard data directory. On macOS, this typically resides in ~/Library/Application Support/com.myapp.app/.

    Structure:

    • preferences.json: Stores the main application preferences.
    • recovery/: A subdirectory containing various JSON files for emergency data (e.g., unsaved-work.json, crash-report-*.json).
  11. Secure Updates with Signature Verification

    main

    To prevent malicious updates, all installers must be cryptographically signed. The application uses the pubkey defined in tauri.conf.json to verify the integrity of the downloaded files.

    Workflow:

    1. Generate Keys: Use the Tauri CLI to generate a key pair: tauri signer generate -w ~/.tauri/myapp.key.
    2. Sign Releases: During the build process (e.g., in GitHub Actions), use the private key to sign the platform-specific installers.
    3. Verify: The @tauri-apps/plugin-updater automatically rejects any update where the signature does not match the public key provided in the configuration.

    Invalid signatures result in an automatic rejection of the update attempt.