tui-lipan 0.2.0

Opinionated, component-based TUI framework for Rust - declarative components, reconciliation, layout engine, focus, overlays, and rich widgets on top of ratatui.
Documentation
# Contributing to tui-lipan

Thanks for considering a contribution! This document covers what you need to
know to land a PR.

## Quick checklist

Before opening a PR:

- [ ] `cargo fmt --all` passes
- [ ] `cargo clippy --workspace --all-targets --all-features -- -D warnings` passes
- [ ] `RUSTDOCFLAGS="-D warnings" cargo doc --workspace --all-features --no-deps` passes
      (catches broken intra-doc links, which the other lints do not)
- [ ] `cargo test --workspace --all-features` passes
- [ ] Macro-body formatting: `./scripts/format-rust-with-macros --check` passes
- [ ] User-visible changes are listed in `CHANGELOG.md` under `[Unreleased]`
- [ ] Docs in `docs/` are updated if the behavior or API surface changed
- [ ] If you added a new widget, all checklist steps in
      [`docs/widget-authoring.md`]docs/widget-authoring.md are completed
- [ ] If `tests/visual_baseline.rs` failed, the rendering change was intended and
      the re-recorded images in `tests/ui-baselines/` are committed
      (see [Visual baselines]#visual-baselines)

Opening the PR fills in [`.github/PULL_REQUEST_TEMPLATE.md`](.github/PULL_REQUEST_TEMPLATE.md)
automatically - keep its checklist.

## Visual baselines

`tests/visual_baseline.rs` renders core widget chrome - frame borders and
headers, focus chrome, input placeholders and masking, list selection - and
compares it against committed reference images in `tests/ui-baselines/`. It
exists so a refactor that quietly moves a border or drops a focus highlight fails
in CI instead of shipping.

A failure is not automatically a bug. Read the `*.diff.png` path named in the
failure message: unchanged pixels are dimmed, changed pixels are magenta, so what
moved is obvious. Then decide.

- **The change was intended** (you restyled a widget): re-record and commit the
  updated images in the same PR.

  ```bash
  TUI_LIPAN_UPDATE_BASELINES=1 cargo test --all-features --test visual_baseline
  ```

- **The change was not intended**: you found a rendering regression. Fix it
  rather than updating the baseline.

Diff images are gitignored; only the baselines themselves are committed.
Comparison always uses the crate's built-in bitmap font rather than a system
font, so results are identical on CI and on every contributor's machine - a
baseline never fails because of which fonts you have installed.

## Pull request titles

PRs are **squash-merged**, so the PR title becomes the commit subject on `main`.
Give it a Conventional Commit title - same format as commits (see below):
`<type>(<optional scope>): <imperative summary>`, `<= 72` chars, no trailing
period. For example: `fix(scroll_view): clip last row on odd content height`.

## Commit messages

Use [Conventional Commits](https://www.conventionalcommits.org/): a `<type>`
(`feat`, `fix`, `docs`, `refactor`, `test`, `style`, `perf`, `chore`, `ci`,
`release`) with an optional scope, an imperative summary, `<= 72` chars, and no
trailing period. Mark breaking changes with a "(breaking)" suffix on the
relevant `CHANGELOG.md` line.

## Toolchain

- **MSRV:** Rust 1.90 (`edition = "2024"`)
- Stable toolchain is expected for all CI jobs

The crate's own code and its default features build on 1.88, but the `image`
feature (and everything that enables it — `terminal-images`,
`image-full-formats`, `clipboard-images` + `ratatui-image`) pulls
`ratatui-image → icy_sixel → quantette`, and `quantette 0.5.1` declares
`rust-version = "1.90"`. `icy_sixel 0.5.0` pins `quantette = "0.5.1"`, so there
is no older resolution that avoids it. `rust-version` is a single
package-level value with no per-feature form, so the manifest declares the
ceiling the full feature set actually needs.

## Local development

```bash
# Format
cargo fmt --all
./scripts/format-rust-with-macros src/ examples/ tests/ benches/ tui-lipan-macro/

# Lint
cargo clippy --workspace --all-targets --all-features -- -D warnings
RUSTDOCFLAGS="-D warnings" cargo doc --workspace --all-features --no-deps

# Test
cargo test --workspace --all-features

# Run an example
cargo run --example showcase
cargo run --example image --features image
cargo run --example markdown_hub --features markdown
```

For faster iteration on examples without paying full release-build costs:

```bash
cargo run --profile dev-fast --example scroll_view_opencode_repro \
    --features "markdown diff-view syntax-syntect"
```

## CHANGELOG policy

Every PR with a user-visible change **must** add an entry under `[Unreleased]`
in `CHANGELOG.md`. The format follows [Keep a Changelog](https://keepachangelog.com/):

```markdown
## [Unreleased]

### Added
- New `Foo` widget with `.bar()` builder.

### Changed
- `Frame` now uses positional `BorderLabels` header/footer groups instead of its
  former title/status API (breaking).

### Fixed
- `ScrollView` no longer clips the last row when content height is odd.

### Removed
- Deprecated `LegacyButton` widget.
```

Use these section headings: **Added**, **Changed**, **Deprecated**, **Removed**,
**Fixed**, **Security**.

**Breaking changes** must say "(breaking)" at the end of the line so they are
trivial to grep at release time.

**Skip the changelog only for:** internal refactors with no API/behavior change,
docs-only changes, CI/tooling changes, test-only changes. When in doubt, add an
entry - it's cheaper than missing one.

## Adding a new widget

Start with a composite widget whenever the UI can be expressed using existing
primitives. New primitive widgets are framework-maintainer work and should meet
the acceptance criteria in [`docs/widget-authoring.md`](docs/widget-authoring.md):
they need custom measurement, node state, rendering, hit testing, or scrollbar
regions; fit the curated built-in set; and cannot be cleanly expressed as a
composite.

The full primitive checklist (which files to touch, in which order) lives in
[`docs/widget-authoring.md`](docs/widget-authoring.md). Skipping any step will
cause a non-obvious panic or render glitch - every match arm in the dispatch
chain is exhaustive.

After implementation:

1. Add a runnable example in `examples/<widget_name>.rs`.
2. Add a per-widget doc page or section in `docs/widgets/`.
3. Add a `CHANGELOG.md` entry under `### Added`.
4. If the widget is feature-gated, register the example in `Cargo.toml`
   under `[[example]]` with `required-features`.

## Releasing (maintainers)

1. Move `[Unreleased]` entries into a new `## [X.Y.Z] - YYYY-MM-DD` section.
2. Bump `version` in both `Cargo.toml` files (root and `tui-lipan-macro/`).
3. Update the dependency line `tui-lipan-macro = { ..., version = "X.Y.Z" }`.
4. Update the comparison links at the bottom of `CHANGELOG.md`.
5. Commit with message `release: vX.Y.Z`, tag `vX.Y.Z`, push the tag.
6. Pushing the tag triggers `.github/workflows/release.yml`, which verifies
   the tag against both crate versions and the changelog, runs the test
   suite, and publishes `tui-lipan-macro` then `tui-lipan` to crates.io via
   crates.io Trusted Publishing - no API token needed. Manual fallback:
   `cargo publish -p tui-lipan-macro`, wait ~30s for the index, then
   `cargo publish -p tui-lipan`.
7. Create a GitHub release referencing the changelog section.

## Filing issues

Bug reports - please include:
- `tui-lipan` version + enabled features
- Terminal emulator and OS
- Minimal reproducer (a small `#[example]` is ideal)

Feature requests - please include:
- The use case (what app you're building, what's blocked)
- A sketch of the API you'd want, even rough

## License and the DCO

tui-lipan is licensed under **MPL-2.0** (see [LICENSE](LICENSE)). Commercial
support and services are also available - see [COMMERCIAL.md](COMMERCIAL.md).

Contributions follow **inbound = outbound**: unless you state otherwise, any
contribution you intentionally submit for inclusion is licensed under the same
**MPL-2.0** as the project, with no additional terms. You retain the copyright
in your contributions - tui-lipan does **not** ask you to assign copyright or
sign a CLA.

Instead, we use the [Developer Certificate of Origin](https://developercertificate.org/)
(DCO): a lightweight, one-line attestation that you wrote the change (or
otherwise have the right to submit it) and agree to license it under MPL-2.0.
Sign off each commit by adding a `Signed-off-by` trailer:

```bash
git commit -s -m "fix(scroll_view): clip last row on odd content height"
```

This appends a line like:

```
Signed-off-by: Your Name <you@example.com>
```

The name and email must be real and match your Git identity. If you forget,
`git commit --amend -s` (or `git rebase --signoff` for a series) adds it.

> **Why DCO over a CLA?** A CLA would let the project relicense your code under
> proprietary terms later. We deliberately don't want that power: keeping
> everything under MPL-2.0 (inbound = outbound) is a promise that the framework
> stays open and cannot be quietly closed. The DCO gives us a clean provenance
> record without taking any extra rights from you.