# Contributing to ltk Thanks for considering a contribution. This document covers the practical mechanics: how to set up your environment, how to run tests, what shape a patch should take, and where to send it. For background on the toolkit itself, read [`docs/onboarding.md`](docs/onboarding.md) and [`docs/architecture.md`](docs/architecture.md) first. ## Reporting bugs and proposing features Open an issue on the project repository before sending a non-trivial patch. We want to align on scope before you spend time on an implementation. For security-relevant issues see [`SECURITY.md`](SECURITY.md) — those should *not* go through the public issue tracker. When reporting a bug, include: - the `ltk` version (commit hash if you built from source), - the Rust toolchain (`rustc --version`), - the compositor and OS, - a minimal reproducer (Rust source preferred over screenshots), - whether the issue happens on both the GLES and the software backend (set `LTK_FORCE_SOFTWARE=1` to force software). ## Building and testing The project requires the Rust toolchain shipped with Debian stable (currently 1.85). On Debian / Ubuntu: ```bash sudo apt-get install \ libwayland-dev libegl-dev libxkbcommon-dev pkg-config git clone cd ltk cargo build cargo test ``` The `Makefile` wraps the common targets: ```bash make all # cargo build --release make test # cargo test make audit # cargo audit (installs cargo-audit on first run) make doc # cargo doc --no-deps make example # run every example under examples/ in turn make clean ``` Running the examples requires a Wayland session and the default theme on disk: ```bash export LTK_THEMES_DIR="$PWD/themes" cargo run --example showcase ``` ## Code style `ltk` uses a custom **Modified Allman** style. `rustfmt`'s default settings do not match it; do not run `cargo fmt`. The full rules live in [`code_style_guide.md`](code_style_guide.md), but the headline points are: - **tabs** for indentation (never spaces), - opening `{` on its **own new line** for `fn`, `impl`, `struct`, `enum`, `mod`, `if`, `for`, `while`, `match`, `loop`, - `} else {` and `} else if … {` on the same line as the closing brace (compact else), - spaces inside non-empty parentheses: `fn foo( x: i32 )`, `bar( arg )`, `Some( x )`, `Ok( v )`, - spaces inside non-empty attribute brackets: `#[ derive( Clone, Debug ) ]`, - no spaces inside `<>` generics: `Vec`, `Option`, - comments in **English** — never another language. Match the surrounding code when in doubt. ## Patch shape - Keep changes focused: one logical change per pull request. A bug fix and a refactor go in separate PRs. - Add tests. The repository has ~400 tests covering the existing surface, and a contribution that adds behaviour without test coverage will get review pushback. See [`tests/`](tests/) for examples of integration tests via `UiSurface`, and the inline `#[cfg(test)] mod tests` blocks under `src/` for unit tests. - Document new public API. Every `pub` item exported from the crate root is expected to have a `///` rustdoc comment with at minimum a one-paragraph description and an example. Module-level `//!` comments are required for new submodules. - Don't break the public API surface without coordinating. Until the crate hits `1.0`, breaking changes go in minor versions (`0.x.0 → 0.(x+1).0`); patch versions (`0.x.y → 0.x.(y+1)`) keep source compatibility. - Run `cargo test` and `make audit` before sending. CI will run them again, but it is faster for both of us if your local run is clean. ## Architectural decisions worth knowing A few patterns recur across the codebase: - **Builder methods consume `self`** (`pub fn padding( mut self, p: f32 ) -> Self`). Chaining works because every builder returns `Self`. Don't introduce setters that take `&mut self`. - **Layouts and widgets share `Element`.** Anything that converts to `Element` can be pushed into any layout. The split between [`crate::layout`] and [`crate::widget`] is documentation, not architecture. - **The runtime is single-threaded.** Use `RefCell` for caches inside `App` state, never `Mutex`. Cross-thread communication goes through [`ChannelSender`](src/app.rs). - **`view()` must be pure.** No I/O, no allocation-heavy work, no state mutation. Cache derived data on the app struct (behind `RefCell` if needed) and look it up. - **Theming is process-global.** There is no per-app theme; the active document and mode live in a `RwLock>`. `view()` reads the state, never writes it. Mode flips and document swaps go through [`crate::set_active_mode`] and [`crate::set_active_document`]. - **Per-frame allocations are fine.** Building the `Element` tree on every render is the supported model. Don't try to retain widgets across frames. ## License By submitting a patch you agree it is licensed under [`LGPL-2.1-only`](LICENSE), the same as the rest of the project. Do not add `Co-Authored-By` lines or other AI attribution to commit messages. ## Code of conduct Be respectful. Disagreements are welcome; personal attacks are not. Maintainers will moderate threads that go off the rails.