Skip to content

Latest commit

 

History

History
178 lines (132 loc) · 8.21 KB

File metadata and controls

178 lines (132 loc) · 8.21 KB

CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

Common Commands

git-sync-desktop (Tauri + React)

# Rust backend only
cargo check -p git-sync-tauri

# Full dev (starts Vite + Tauri together)
pnpm tauri dev

# Production build
pnpm tauri build

# Frontend typecheck
pnpm exec tsc --noEmit

Workspace

# Tests (minimal coverage — only git-sync-lib has a placeholder test)
cargo test

# Regenerate src/bindings.ts (tauri-specta) without starting the full app
cargo test -p git-sync-tauri export_bindings

Architecture

Workspace

Two crates:

  • crates/git-sync-lib — vendored fork of git-sync-rs; all git sync logic (WatchManager, RepositorySynchronizer, SyncState, RepositoryState)
  • src-tauri — desktop app using Tauri v2 + React frontend (crate name: git-sync-tauri)

Config file: ~/.config/Cognito Forms/Git Sync/desktop.toml.


git-sync-desktop (Tauri v2 + React)

Structure

<repo root>/
├── src-tauri/          ← Rust backend (workspace crate: git-sync-tauri)
│   ├── src/
│   │   ├── lib.rs          ← Tauri setup, tray, worker spawn, status forwarder
│   │   ├── main.rs         ← Entry point
│   │   ├── commands.rs     ← #[tauri::command] handlers
│   │   ├── worker.rs       ← Background sync loop
│   │   ├── config.rs       ← DesktopConfig + TOML load/save
│   │   ├── status.rs       ← AppStatus/RepoStatus with Serialize
│   │   └── log_layer.rs    ← Logging setup
│   ├── tauri.conf.json
│   └── capabilities/default.json
├── src/                ← React + TypeScript frontend (Vite)
│   ├── main.tsx        ← Entry; wraps app in QueryClientProvider + ThemeProvider
│   ├── App.tsx         ← Root; aggregates status, owns navigation state
│   ├── bindings.ts     ← Auto-generated by tauri-specta (do not edit)
│   ├── api.ts          ← Re-exports commands/events from bindings + formatLastSync
│   ├── types.ts        ← View type only (IPC types live in bindings.ts)
│   ├── index.css       ← Tailwind v4 + shadcn CSS variables (neutral theme)
│   ├── hooks/
│   │   ├── queries.ts          ← TanStack Query hooks (useConfig, useStatus, useSetConfig, useSyncNow) + ResolvedConfig types
│   │   ├── useIsFullscreen.ts  ← Detects macOS fullscreen state
│   │   └── utils.ts
│   ├── components/
│   │   ├── TitleBar.tsx            ← Custom title bar: drag region, theme toggle, window controls
│   │   ├── ThemeProvider.tsx       ← Light/dark/system theme context + localStorage persistence
│   │   ├── RepoListView.tsx        ← Repository table with live status
│   │   ├── RepoSettingsView.tsx    ← Per-repo config form
│   │   ├── RepoDetailSidebar.tsx   ← Log viewer sidebar
│   │   ├── AboutModal.tsx          ← Modal dialog
│   │   ├── RepoStatusBadge.tsx     ← Status badge component
│   │   └── StatusDot.tsx           ← Colored dot by sync state ID
│   └── components/ui/
│       └── button.tsx, checkbox.tsx, field.tsx, input.tsx, label.tsx, separator.tsx, sonner.tsx
└── package.json
└── crates/
    └── git-sync-lib/   ← Core sync library

Rust backend IPC

IPC is typed end-to-end via tauri-specta (v2.0.0-rc.24). Rust types and commands annotated with specta::Type / #[specta::specta] are exported to src/bindings.ts at build time. Never edit bindings.ts manually — regenerate it with cargo test -p git-sync-tauri export_bindings.

Tauri commands (called from frontend via commands.* in bindings.ts):

Command Description
get_config Returns full DesktopConfig
get_status Returns current AppStatus snapshot
set_config(config) Saves config to TOML + sends Reconfigure to worker
sync_now(index) Sends SyncNow(index) to worker
validate_repo_path(path) Returns true if path contains a .git directory
pick_folder Opens native folder picker (tauri-plugin-dialog)
get_log_history(repo) Returns buffered log entries, optionally filtered by repo path

Result<T, E> commands return a tagged union { status: "ok"; data: T } | { status: "error"; error: E } — check .status before using .data.

Tauri events (emitted from Rust, listened via events.* in bindings.ts):

Event Payload Description
StatusUpdateEvent AppStatus Pushed on every worker status change
LogEntryEvent FrontendLogEntry Pushed for each log line forwarded to the frontend

Adding new IPC:

  1. Add specta::Type to any new Rust struct/enum
  2. Add #[tauri::command] + #[specta::specta] to the function, register it in the collect_commands! macro in lib.rs
  3. Run cargo test -p git-sync-tauri export_bindings to regenerate bindings.ts

Threading model

Thread Role
Main (Tauri) Tauri event loop; manages state, tray, window events
Background (std + tokio) run_background() in worker.rs; single-threaded tokio + LocalSet
Async (Tauri runtime) Status forwarder: watches watch::Receiver and emits StatusUpdateEvent; log forwarder emits LogEntryEvent

Worker pattern: one task per repo, auto-respawns after 2 s on error.

Custom title bar

Window has decorations: false. The React TitleBar component provides:

  • data-tauri-drag-region on the content div (requires core:window:allow-start-dragging capability)
  • Theme toggle (Sun/Moon) — cycles light ↔ dark, persists to localStorage
  • Window controls (Minus/Square/X) via @tauri-apps/api/window
  • Close button calls appWindow.close() → Rust CloseRequested handler → hides to tray

Dark mode

ThemeProvider in src/components/ThemeProvider.tsx:

  • Reads/writes "git-sync-theme" key in localStorage
  • Defaults to system preference (prefers-color-scheme)
  • Applies/removes .dark class on document.documentElement
  • Listens to OS theme changes when set to "system"

Frontend tech stack

  • Vite + React 19 + TypeScript
  • Tailwind CSS v4 via @tailwindcss/vite plugin
  • shadcn/ui (radix-lyra style, neutral base color, phosphor icons)
  • @phosphor-icons/react for all icons
  • TanStack Query v5 for server state (useConfig, useStatus, useSetConfig, useSyncNow in src/hooks/queries.ts)
  • Package manager: pnpm

Config type normalization

RepoConfig and GlobalSettings use #[serde(default)] in Rust, which causes tauri-specta to generate all their fields as optional in TypeScript. normalizeConfig() in queries.ts fills in defaults on the way in and returns ResolvedConfig (with ResolvedGlobal and ResolvedRepo subtypes) where all fields are required. Components should use these resolved types rather than the raw bindings types.

Plugins required

Plugin Purpose
tauri-plugin-dialog Native folder picker
tauri-plugin-os OS detection (platform info)

Capabilities (capabilities/default.json)

"core:default",
"core:window:allow-start-dragging",
"dialog:allow-open",
"os:default"