A Tauri desktop app for orchestrating git worktrees with integrated terminal support.
- Frontend: React 19, TypeScript, Tailwind CSS, xterm.js
- Backend: Rust, Tauri 2.x
- Build: Vite, Cargo
src/ # React frontend
components/ # UI components
hooks/ # React hooks (useWorktrees, usePty, useGitStatus)
lib/ # Tauri invoke wrappers
types/ # TypeScript types
src-tauri/ # Rust backend
src/
lib.rs # Tauri commands
worktree.rs # Git worktree operations
pty.rs # Terminal/PTY management
watcher.rs # File system watcher
config.rs # User configuration
state.rs # App state types
npm install
npm run tauri devImportant: Do not run the app unless asked to verify logs or test specific behavior. The user will typically run and test the app themselves.
When adding new functionality, implement it as an action so it appears in the command palette. This ensures all features are discoverable and accessible via keyboard. Only skip this if there's a good reason (e.g., the feature is purely internal or doesn't make sense as a user-invokable action).
Actions use namespaced format (e.g., diff::open, worktree::new). To add a new action, update these files:
-
src/lib/actions.ts- Action registry (3 places):- Add to
ActionIdtype union - Add availability predicate in
AVAILABILITYrecord - Add metadata in
ACTION_METADATA(label, category, showInPalette)
- Add to
-
src/lib/actionHandlers.ts- Handler wiring (2 places):- Add callback to
ActionHandlerCallbacksinterface - Add mapping in
createActionHandlers()function
- Add callback to
-
src/lib/defaultMappings.jsonc- Keyboard shortcut:- Add binding in appropriate context section
-
src/App.tsx- Implementation (3 places):- Create handler function (e.g.,
handleOpenDiff) - Add to
actionHandlersuseMemo - Add to
contextActionHandlersuseMemo (withcreateActionHandlers()call)
- Create handler function (e.g.,
Available context flags for keybindings (src/lib/contexts.ts):
scratchFocused,worktreeFocused,projectFocuseddrawerFocused,mainFocuseddrawerOpen,rightPanelOpenpickerOpen,commandPaletteOpen,modalOpendiffViewOpen,canGoBack,canGoForward
Available context for availability predicates:
activeProjectId,activeWorktreeId,activeScratchId,activeEntityIdisDrawerOpen,isDrawerFocused,activeDrawerTabIdopenEntityCount,canGoBack,canGoForwardisViewingDiff,changedFilesCountactiveSelectedTask,taskCount
Always write tests for new functionality and bug fixes. After making changes, explicitly state whether tests were added and run them to verify they pass.
# TypeScript/React tests (Vitest)
npm test
# Rust tests
cd src-tauri && cargo test- Frontend tests:
src/**/*.test.ts(x)- Uses Vitest with mocked Tauri APIs - Backend tests:
src-tauri/src/*.rs- Uses#[cfg(test)]modules with#[test]functions
- Add tests for new features covering the happy path and edge cases
- Add regression tests for bug fixes to prevent recurrence
- Use existing test files as patterns (e.g.,
usePty.test.tsfor hooks) - Mock Tauri APIs using the setup in
src/test/setup.ts - Use
data-testidattributes to query elements in tests, not CSS classes (Tailwind classes are brittle and change frequently)
This project uses Conventional Commits with release-please for automated versioning.
<type>: <description>
[optional body]
[optional footer]
| Commit Type | Version Bump | Example |
|---|---|---|
feat: |
Minor (0.1.0 → 0.2.0) | feat: add dark mode toggle |
fix: |
Patch (0.1.0 → 0.1.1) | fix: resolve crash on startup |
feat!: or BREAKING CHANGE: |
Major (0.1.0 → 1.0.0) | feat!: change config format |
docs:, chore:, refactor:, test:, style: |
No bump | docs: update README |
feat- New feature for the userfix- Bug fix for the userdocs- Documentation only changesstyle- Formatting, missing semicolons, etc (no code change)refactor- Code change that neither fixes a bug nor adds a featureperf- Performance improvementtest- Adding or updating testschore- Maintenance tasks, dependency updatesbuild- Changes to build system or external dependenciesci- CI configuration changes
- Make commits using conventional commit format
- Push to
mainbranch - Release-please automatically creates/updates a release PR
- Review and merge the release PR
- GitHub Action builds and uploads binaries to the release
Version is defined in three places that release-please keeps in sync:
package.json-versionfieldsrc-tauri/Cargo.toml-versionfieldsrc-tauri/tauri.conf.json-versionfield
All logs (Rust backend + TypeScript frontend) are unified for easy debugging.
| Source | Terminal (stdout) | Log File | Browser DevTools |
|---|---|---|---|
Rust log::info!() |
❌ | ✅ | ✅ |
Rust eprintln!() |
✅ (stderr) | ❌ | ❌ |
TS log.info() |
✅ | ✅ | ✅ |
TS console.log() |
❌ | ❌ | ✅ |
Use log.info() for TypeScript logs - they appear everywhere.
# Live tail during development
tail -f ~/Library/Logs/com.shellflow.desktop/shellflow.log
# Read full log
cat ~/Library/Logs/com.shellflow.desktop/shellflow.logRust - Use the log crate macros (goes to file + browser):
use log::{info, warn, error, debug, trace};
info!("[function_name] Starting operation...");
error!("[function_name] Failed: {}", e);TypeScript - Use the log utility (goes to terminal + file + browser):
import { log } from '../lib/log';
log.info('[ComponentName] Mounting with props:', props);
log.warn('[ComponentName] Unexpected state:', state);
log.error('[ComponentName] Failed to fetch:', error);- Always prefix with context - Use
[function_name]or[ComponentName]prefix - Log timing for operations - Wrap slow operations with
Instant::now()/performance.now() - Log state transitions - When important state changes, log before/after
- Log errors with context - Include relevant IDs, paths, or parameters
- Use appropriate levels:
log.error/error!- Failures that need attentionlog.warn/warn!- Unexpected but recoverable situationslog.info/info!- Key operations and timinglog.debug/debug!- Detailed debugging (not shown by default)
let start = std::time::Instant::now();
// ... operation ...
info!("[operation_name] took {:?}", start.elapsed());const start = performance.now();
// ... operation ...
log.info(`[operationName] took ${performance.now() - start}ms`);User config is stored at ~/.config/shellflow/config.jsonc:
When adding new configurable options, update all three files:
src-tauri/src/config.rs- Rust struct and defaultssrc-tauri/src/default_config.jsonc- Default config with commentsschemas/config.schema.json- JSON Schema for validation
{ "main": { "command": "claude", // Command to run in main pane "fontFamily": "Menlo, Monaco, monospace", "fontSize": 13 }, "worktree": { "directory": "{{ repo_directory }}/.worktrees", "copy": { "gitignored": true, "except": [".claude"] } } }