# Contributing to par-term
> **Note:** [CLAUDE.md](CLAUDE.md) is the canonical development reference. It is kept up-to-date by AI-assisted tooling and covers build commands, architecture, debugging workflows, and common gotchas in more detail than this file. When the two documents disagree, CLAUDE.md takes precedence.
Guidelines for contributing to par-term, a cross-platform GPU-accelerated terminal emulator built in Rust.
## Table of Contents
- [Overview](#overview)
- [Development Setup](#development-setup)
- [Prerequisites](#prerequisites)
- [Platform-Specific Requirements](#platform-specific-requirements)
- [Cloning and Initial Build](#cloning-and-initial-build)
- [Build Commands](#build-commands)
- [Running the Application](#running-the-application)
- [Testing](#testing)
- [Code Quality](#code-quality)
- [Debug Logging](#debug-logging)
- [Architecture Overview](#architecture-overview)
- [Code Style](#code-style)
- [macOS-Specific Notes](#macos-specific-notes)
- [Linux-Specific Notes](#linux-specific-notes)
- [Windows-Specific Notes](#windows-specific-notes)
- [Configuration](#configuration)
- [Common Development Workflows](#common-development-workflows)
- [Adding a Configuration Option](#adding-a-configuration-option)
- [Adding a Keyboard Shortcut](#adding-a-keyboard-shortcut)
- [Sub-Crate Architecture](#sub-crate-architecture)
- [Dependency Layers](#dependency-layers)
- [Version Bump Checklist](#version-bump-checklist)
- [PR Guidelines](#pr-guidelines)
- [Commit Message Format](#commit-message-format)
- [Related Documentation](#related-documentation)
## Overview
par-term is a cross-platform GPU-accelerated terminal emulator frontend written in Rust (Edition 2024). It uses the [par-term-emu-core-rust](https://github.com/paulrobello/par-term-emu-core-rust) library for VT sequence processing, PTY management, and inline graphics protocols (Sixel, iTerm2, Kitty). The frontend provides GPU-accelerated rendering via `wgpu` with custom WGSL shaders, including support for Ghostty/Shadertoy-compatible GLSL post-processing shaders.
**Language:** Rust (Edition 2024)
**License:** MIT
**Platforms:** macOS (Metal), Linux (Vulkan), Windows (DirectX 12)
## Development Setup
### Prerequisites
- **Rust stable toolchain** — install via [rustup](https://rustup.rs/):
```bash
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
```
- **Make** for build orchestration (available by default on macOS and most Linux distributions)
- **Git**
### Platform-Specific Requirements
#### macOS
No additional system libraries are required. The Metal GPU backend is used automatically.
#### Linux
Install the required X11/Wayland and SSL development libraries before building:
```bash
# Debian / Ubuntu
sudo apt-get install \
libxcb-render0-dev \
libxcb-shape0-dev \
libxcb-xfixes0-dev \
libxkbcommon-dev \
libssl-dev \
pkg-config
# Fedora / RHEL
sudo dnf install \
libxcb-devel \
libxkbcommon-devel \
openssl-devel \
pkg-config
```
#### Windows
No additional steps are required beyond a working Rust stable toolchain. The DirectX 12 backend is used automatically.
### Cloning and Initial Build
```bash
git clone https://github.com/paulrobello/par-term.git
cd par-term
make build
```
The first build downloads and compiles all dependencies, which takes several minutes. Subsequent incremental builds complete in roughly 1–2 seconds with `make build`.
## Build Commands
> **Use `make build` for all day-to-day development.** The `dev-release` Cargo profile (opt-level 2, no LTO, 16 codegen-units, incremental) delivers approximately 90–95% of full-release performance with ~1–2s incremental rebuilds. Only switch to `make build-full` when preparing distribution binaries.
| Command | Cargo Profile | Compile Time | Description |
|---------|---------------|--------------|-------------|
| `make build` | `dev-release` | ~1-2s (incremental) | Optimized, incremental. **Preferred for development.** |
| `make build-full` | `release` | ~3 min | Full LTO, single codegen unit. Use for distribution. |
| `make build-debug` | `debug` | Fast | Unoptimized, includes debug symbols. Use when stepping through code with a debugger. |
| `make release` | `release` | ~3 min | Alias for `make build-full`. |
Output binary locations:
| `dev-release` | `target/dev-release/par-term` |
| `release` | `target/release/par-term` |
| `debug` | `target/debug/par-term` |
## Running the Application
```bash
make run # Run in dev-release mode (preferred)
make run-release # Run in full release mode
```
> **Warning:** When stopping a running debug instance, never use `killall par-term`. This kills all par-term processes on the system, including the terminal session you are working in. Use `pkill -f "target/debug/par-term"` or kill by specific PID instead.
When testing your code changes, always run from the build output (`make run`, `cargo run`) rather than from an installed `.app` bundle. An installed bundle will not contain your local changes.
## Testing
```bash
make test # Run all workspace tests
make test-one TEST=test_name # Run a specific test by name
make test-verbose # Run tests with captured output printed to the terminal
cargo test -- --include-ignored # Run all tests, including PTY-dependent ones
```
### PTY-Dependent Tests
Some tests require an active PTY session and are marked `#[ignore]`. The default `make test` run skips them. To include them explicitly:
```bash
cargo test -- --include-ignored
```
### Integration Tests
Integration tests live in the `tests/` directory and cover configuration loading, terminal state, and input handling. They use the `tempfile` crate for isolated temporary files and do not modify real user configuration.
### Specialized Testing Targets
```bash
make test-fonts # Comprehensive text shaping and font rendering test suite
make benchmark-shaping # Text shaping performance benchmarks
make test-text-shaping # Run font tests and benchmarks together
make test-graphics # Graphics protocol tests with DEBUG_LEVEL=4 logging
make test-animations # Kitty animation protocol tests
```
## Code Quality
Run the full quality suite before every commit:
```bash
make checkall # Format, lint, and test — required before committing
```
Individual targets:
| `make fmt` | `cargo fmt` | Format all code with rustfmt |
| `make fmt-check` | `cargo fmt -- --check` | Check formatting without modifying files |
| `make lint` | `cargo clippy -- -D warnings` | Lint with warnings treated as errors |
| `make lint-all` | `cargo clippy --all-targets -- -D warnings` | Lint all targets (bins, tests, examples, benches) |
| `make check` | `cargo check` | Type-check without producing a binary |
| `make check-all` | `cargo check --all-targets` | Type-check all targets |
### Pre-Commit and CI Targets
```bash
make pre-commit # fmt-check + lint + test — run this locally before pushing
make ci # fmt-check + lint-all + test + check-all — equivalent to CI checks
make all # fmt + lint + test + build
```
### Optional Tools
```bash
make coverage # Test coverage report via cargo-tarpaulin (must be installed separately)
make audit # Dependency security audit via cargo-audit (must be installed separately)
make profile # CPU profiling with cargo-flamegraph (must be installed separately)
make doc-open # Generate and open rustdoc documentation in a browser
```
## Debug Logging
par-term uses a custom debug macro system that writes to `/tmp/par_term_debug.log`. The standard `log` crate macros (`log::info!()`, `log::warn!()`, etc.) write to stdout and will not appear in the debug log file.
### Running with Debug Output
In one terminal, start par-term with debug logging enabled:
```bash
make run-debug # DEBUG_LEVEL=3 — standard debug output → /tmp/par_term_debug.log
make run-trace # DEBUG_LEVEL=4 — most verbose, includes trace events
```
In a second terminal, monitor the log in real time:
```bash
make tail-log # Stream all log entries
make watch-graphics # Stream only graphics-related log entries
```
### Debug Macro Reference
Always use these project-specific macros when adding log output to source code:
```rust
crate::debug_error!("CATEGORY", "message {}", var); // DEBUG_LEVEL ≥ 1 — errors
crate::debug_info!("CATEGORY", "message {}", var); // DEBUG_LEVEL ≥ 2 — informational
crate::debug_log!("CATEGORY", "message"); // DEBUG_LEVEL ≥ 3 — standard debug
crate::debug_trace!("CATEGORY", "message"); // DEBUG_LEVEL = 4 — trace/verbose
```
Common log categories: `TAB`, `TAB_BAR`, `TAB_ACTION`, `MOUSE`, `RENDER`, `SHADER`, `TERMINAL`, `APP`
For the full logging documentation, see `docs/LOGGING.md`.
### ACP Agent Debugging
When debugging the Assistant Panel (Claude/Ollama ACP integration):
```bash
make acp-harness ARGS="--list-agents" # Verify agent discovery and config loading
make acp-smoke # Run the reproducible shader prompt smoke test
```
See `docs/ACP_HARNESS.md` for full usage details and transcript capture.
## Architecture Overview
For a detailed description of the codebase structure, data flow, threading model, and GPU rendering pipeline, see `docs/architecture/ARCHITECTURE.md`.
**Key layers:**
```
App (src/app/)
└── Terminal (par-term-terminal/src/)
└── Renderer (par-term-render/src/)
└── GPU Shaders (par-term-render/src/shaders/)
```
**Data flow:**
```
Window Events → Input Handler → PTY → VT Parser → Styled Segments
→ GPU Renderer (three passes: cells → graphics → egui overlay)
```
**Key implementation patterns:**
- Tokio runtime for async PTY I/O; sync wrappers bridge to the winit event loop
- Glyph atlas with instanced rendering for efficient text output
- RGBA texture caching for inline graphics (Sixel, iTerm2, Kitty)
- Scrollback buffer with viewport offset rendering
## Code Style
### File Size Targets
- **Target:** Keep all source files under 500 lines.
- **Refactor threshold:** Any file exceeding 800 lines must be split into sub-modules.
- Follow the existing patterns: `src/app/`, `par-term-terminal/src/`, `par-term-render/src/`.
- Centralize constants; avoid magic numbers scattered across files.
- Prefer composition over duplication; create helper traits for shared functionality.
### Documentation Standards
Use `///` doc comments for all public API items (structs, enums, traits, public functions and methods). Use `//` inline comments for implementation notes and non-obvious logic. Focus comments on *why* rather than *what* — the code already shows what is happening.
```rust
/// Renders all visible cells for the current viewport into the GPU instance buffer.
///
/// Must be called once per frame before `submit_render_pass`.
pub fn upload_cells(&mut self, cells: &[StyledCell]) { ... }
// Skip zero-width cells — they have no visual representation and will corrupt
// the atlas packing algorithm if submitted.
if cell.width == 0 {
continue;
}
```
### Concurrency Rules
- Use `try_lock()` from synchronous contexts when accessing `tab.terminal` (`tokio::sync::Mutex`).
- For user-initiated operations (e.g., start/stop coprocess), use `blocking_lock()` instead.
- Never call `process::exit()` directly — use the graceful shutdown path.
## macOS-Specific Notes
- **GPU backend:** Metal
- **Platform-specific code:** `src/macos_metal.rs`
- **App bundle commands:**
```bash
make bundle make bundle-install make run-bundle ```
- When testing code changes, always run from the build output (`make run`) rather than the installed `.app` bundle. The bundle will not reflect your local changes.
## Linux-Specific Notes
- **GPU backend:** Vulkan
- Both X11 and Wayland display servers are supported.
- Install the required system libraries listed in [Platform-Specific Requirements](#platform-specific-requirements) before your first build.
## Windows-Specific Notes
- **GPU backend:** DirectX 12
- No additional system libraries are required beyond the Rust stable toolchain.
## Configuration
The configuration file uses the XDG base directory convention:
- **Linux / macOS:** `~/.config/par-term/config.yaml`
- **Windows:** `%APPDATA%\par-term\config.yaml`
See `par-term-config/src/config/config_struct/mod.rs` for all available settings and their defaults. For a human-readable reference, see `docs/CONFIG_REFERENCE.md`.
To generate an example configuration file:
```bash
make config-example # Writes config.yaml.example to the project root
```
## Common Development Workflows
### Adding a Configuration Option
1. Add the field to the `Config` struct in `par-term-config/src/config/config_struct/mod.rs` with a serde default attribute:
```rust
#[serde(default = "default_my_option")]
pub my_option: MyType,
```
2. Implement the `default_my_option` function and update the `Default` impl.
3. Use the config value in the relevant component.
4. **Required:** Add UI controls in the appropriate settings tab (`par-term-settings-ui/src/*_tab.rs`). Set `settings.has_changes = true` and `*changes_this_frame = true` when the value changes.
5. **Required:** Update the search keywords in `par-term-settings-ui/src/sidebar.rs` inside `tab_search_keywords()`.
### Adding a Keyboard Shortcut
1. Add key handling in `src/app/input_events/` (a directory with 6+ files; the main dispatch logic is in `keybinding_actions.rs`).
2. If the shortcut generates a terminal sequence, add sequence generation in `src/input.rs` via `InputHandler`.
For snippet or action keybindings, see `docs/features/SNIPPETS.md`. Key points:
- Snippets use `snippet:<id>`, actions use `action:<id>` as keybinding action names.
- Bindings are auto-generated during config load via `generate_snippet_action_keybindings()`.
- `execute_keybinding_action()` in `src/app/input_events/keybinding_actions.rs` handles execution.
## Sub-Crate Architecture
par-term is organized as a Cargo workspace with 14 sub-crates plus the root application crate. The dependency graph is a strict layered DAG.
### Dependency Layers
| **Layer 0** | `par-term-acp`, `par-term-ssh`, `par-term-mcp` | No internal deps; bump in any order |
| **Layer 1** | `par-term-config` | Foundation; depends only on external `par-term-emu-core-rust` |
| **Layer 2** | `par-term-fonts`, `par-term-input`, `par-term-keybindings`, `par-term-scripting`, `par-term-settings-ui`, `par-term-terminal`, `par-term-tmux`, `par-term-update` | All depend on `par-term-config` |
| **Layer 3** | `par-term-render` | Depends on `par-term-config` and `par-term-fonts` |
| **Layer 4** | `par-term` (root) | Depends on all of the above |
### Version Bump Checklist
When bumping sub-crate versions for crates.io publishing, follow dependency order. Update both the crate's own `version` field and any `version = "..."` in dependents' `Cargo.toml` references.
1. Bump `par-term-config` version and update references in all Layer 2/3 crates
2. Bump Layer 0 crate versions
3. Bump Layer 2 crate versions
4. Bump `par-term-render` version and update its `par-term-fonts` reference
5. Update all version references in root `Cargo.toml`
6. Run `cargo check` to verify
## PR Guidelines
1. Fork the repository and create a feature branch from `main`.
2. Make atomic commits — one logical change per commit.
3. Run `make checkall` and confirm it passes without errors before pushing.
4. Open a pull request against `main`.
5. Describe your changes clearly in the PR body. The PR title and description become the squash commit message on main.
6. PRs are merged via **squash merge** to keep the main branch history linear. Feature branches are deleted after merge.
### What "Atomic" Means
Each commit should:
- Compile and pass all tests on its own
- Have a single, clear purpose that can be stated in one sentence
- Not mix refactoring with behavior changes
### Before Submitting
- `make checkall` passes without errors or warnings
- New behavior is covered by tests where practical
- Public API additions carry `///` doc comments
- Settings additions update both the settings tab and the search keywords
## Commit Message Format
```
<type>(<scope>): <subject>
[optional body — 72-character wrapped lines explaining what and why]
[optional footer — e.g., Closes #123]
```
### Commit Types
| `feat` | A new feature |
| `fix` | A bug fix |
| `perf` | A performance improvement |
| `refactor` | Code restructuring with no behavior change |
| `test` | Adding or correcting tests |
| `docs` | Documentation only |
| `style` | Formatting or whitespace, no logic change |
| `chore` | Build system, dependency updates, tooling |
### Rules
- Subject line: maximum 50 characters, imperative mood ("add", "fix", "remove"), no trailing period.
- Body: wrap at 72 characters; explain *why*, not *what*.
- Footer: reference issues with `Closes #123` or `Fixes #456`.
### Examples
```
feat(tab-bar): add drag-and-drop reordering for tabs
Users can now drag tabs to reorder them within the tab bar.
Reorder state is persisted to the session arrangement file
so it survives restarts.
Closes #247
```
```
fix(renderer): clamp scrollback offset to valid range
A race between the PTY reader and the render thread could
produce a viewport offset larger than the scrollback buffer,
causing an out-of-bounds read in the cell upload path.
```
```
perf(render): pre-allocate GPU instance buffers
Avoids per-frame Vec reallocation for the cell and graphics
instance arrays by reserving capacity at startup based on
the initial terminal dimensions.
```
## Related Documentation
- [docs/ARCHITECTURE.md](docs/architecture/ARCHITECTURE.md) — Full architecture, data flow, and threading model
- [docs/LOGGING.md](docs/LOGGING.md) — Custom debug macro system and log levels
- [docs/CUSTOM_SHADERS.md](docs/features/CUSTOM_SHADERS.md) — Background and cursor shader development
- [docs/CONFIG_REFERENCE.md](docs/CONFIG_REFERENCE.md) — All configuration options
- [docs/KEYBOARD_SHORTCUTS.md](docs/guides/KEYBOARD_SHORTCUTS.md) — Default keybinding reference
- [docs/SNIPPETS.md](docs/features/SNIPPETS.md) — Snippet and action keybinding system
- [docs/ACP_HARNESS.md](docs/ACP_HARNESS.md) — ACP agent debugging harness
- [docs/TROUBLESHOOTING.md](docs/guides/TROUBLESHOOTING.md) — Common issues and solutions
- [docs/DOCUMENTATION_STYLE_GUIDE.md](docs/DOCUMENTATION_STYLE_GUIDE.md) — Documentation conventions for this project
- [CLAUDE.md](CLAUDE.md) — Full AI assistant development context and workflow reference