This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
# 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# 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_bindingsTwo 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.
<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
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:
- Add
specta::Typeto any new Rust struct/enum - Add
#[tauri::command]+#[specta::specta]to the function, register it in thecollect_commands!macro inlib.rs - Run
cargo test -p git-sync-tauri export_bindingsto regeneratebindings.ts
| 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.
Window has decorations: false. The React TitleBar component provides:
data-tauri-drag-regionon the content div (requirescore:window:allow-start-draggingcapability)- 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()→ RustCloseRequestedhandler → hides to tray
ThemeProvider in src/components/ThemeProvider.tsx:
- Reads/writes
"git-sync-theme"key inlocalStorage - Defaults to system preference (
prefers-color-scheme) - Applies/removes
.darkclass ondocument.documentElement - Listens to OS theme changes when set to
"system"
- Vite + React 19 + TypeScript
- Tailwind CSS v4 via
@tailwindcss/viteplugin - 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,useSyncNowinsrc/hooks/queries.ts) - Package manager: pnpm
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.
| Plugin | Purpose |
|---|---|
tauri-plugin-dialog |
Native folder picker |
tauri-plugin-os |
OS detection (platform info) |
"core:default",
"core:window:allow-start-dragging",
"dialog:allow-open",
"os:default"