This project uses Jujutsu for version control, not git.
| Dependency | Version |
|---|---|
| macOS | 15+ (Sequoia) |
| Rust | 1.85+ |
| Xcode | 16+ |
| jj | latest |
| just | latest |
| xcodegen | latest |
| xcbeautify | latest |
just test # Rust unit tests
just test-app # Swift unit tests
just test-ui # XCUITest scenes (builds fixtures, sets onboarding default)
just lint # Clippy + SwiftLint
just format # cargo fmt + SwiftFormat
just clean # Remove generated build artifacts
just build # Build the macOS app
just run # Build and run macOS appEvery new feature ships with both unit and UI test coverage. Bug fixes add the regression test that would have caught them.
- Rust unit tests — cover core logic in
crates/jayjay-core/. Run withjust test. - Swift unit tests — cover ViewModel-level behavior in
shell/mac/Tests/JayJayTests/. Run withjust test-app. - XCUITest scenes — cover user-visible flows in
shell/mac/Tests/JayJayUITests/. Run withjust test-ui.
UI tests launch the app against deterministic fixtures at /tmp/jayjay-test-fixtures/{simple,conflict} built by just shell::ui-test-setup. Each scene subclasses SceneBase and asserts against accessibility identifiers declared in shell/mac/Sources/JayJay/Shared/AccessibilityIdentifiers.swift. When adding a new user-visible view or interaction:
- Attach a stable
.accessibilityIdentifier(...)to the view, keyed by the data that makes it unique (change-id prefix, file path, etc.). Add a constant/function toAIDso tests and views share the same string. - Write a scene test under
Tests/JayJayUITests/Scenes/that exercises the flow end-to-end. - If the scene needs fixture state that
simple/conflictdon't provide, extendui-test-setupinshell/justfile.
Rust (crates/) Swift (shell/mac/)
├── jayjay-core ├── App/ Config, Window, Watcher
│ ├── repo (log, diff, ├── Repo/ ViewModel, DAG, CommitBox
│ │ mutations, bookmarks, ├── Detail/ files, tree view
│ │ git, working_copy, ├── Diff/ unified, side-by-side
│ │ undo) ├── Settings/ prefs, jj config, about
│ ├── diff (LCS + word) ├── Onboarding/ welcome flow
│ └── syntax (18 languages) └── Shared/ reusable components
├── jayjay-uniffi ──── FFI ────
└── jayjay-cli (launcher)
| Layer | Tech | Role |
|---|---|---|
| Model | Rust + jj-lib | Business logic, diff, syntax |
| Bindings | uniffi | Rust to Swift type bridge |
| ViewModel | @Observable |
Async operations, state |
| View | SwiftUI + AppKit | Rendering |
JayJay uses both.
Prefer jj-lib for:
- Structured reads and graph data: log, show, diff, bookmarks, diff stats
- Repo mutations where we need typed state, tree access, or custom composition
- New features that need reusable primitives in Rust, especially anything the UI will build on repeatedly
Prefer the jj CLI for:
- Features that are already stable in jj but awkward or unavailable in
jj-lib - External-tool flows such as
jj resolve --tool - Operations where JayJay is intentionally delegating to jj's own behavior and output
Current jj-lib-backed areas:
- Log, revset parsing, show/diff, bookmark data, diffedit application, most core mutations, working-copy refresh
Current jj CLI-backed areas:
resolve,workspace,undo(jj op),split,graft,duplicate,absorb,backout, parts of Git integration, AI commit-message helpers
When adding a feature:
- Put business logic in Rust first.
- Use
jj-libif it gives us a clear typed implementation. - Fall back to
jjCLI when the library path is missing, unstable, or significantly more complex. - Document the choice here if it introduces a new long-term backend pattern.
When a feature lands:
- Update README.md if it changes what users can do today.
- Update Roadmap.md if it changes planned vs shipped status.
- Update this file if it changes architecture, contributor workflow, the testing layout, or the
jj-libvsjjCLI split.
- DeepWiki for indexed codebase docs and architecture browsing Useful when you need a quick high-level map before reading the source directly.
See AGENTS.md for development guidelines.