packdiff-dto 0.3.5

The packdiff data model: diff + review documents, parser, operations, exports. Pure logic, no I/O.
Documentation

packdiff

Dependency-free zero-latency browser-first github-looking diff for git.

Rationale:

  1. Browser is a good enough diff tool.
  2. Github is far too slow.
  3. Not everything is worthy of pushing.

So this tool is mostly to make it more comfortable to review (and comment on!) agents-generated code.


Turn any two branches of a git repository into one self-contained HTML review page — with line comments that live in your browser and export as JSON, Markdown, or CSV.

$ packdiff main my-feature -C ~/src/myrepo -o review.html
Wrote review.html (12 files, +345 −67, 5 commits)

Open review.html anywhere — from disk (file://), over any static host, or mailed to a colleague. The page makes zero network requests: CSS, the diff, and the comment engine are all inside the one file.

  • Comment from the gutter (+ on every commentable line; Ctrl/Cmd+Enter saves; markdown with Write/Preview) — works in unified, split, and rendered Markdown. Comments persist in localStorage, keyed to the exact repo + commit SHAs.
  • Review workspace layout: compact sticky chrome, persistent file sidebar (search, filters, viewed progress), enriched file headers, and a review-summary drawer for the final pass before export.
  • Markdown files render: .md files open in the rendered view (added green, removed red); a Preview | Diff pill switches while preserving place.
  • Filter by commits: checkboxes or click/Shift-click select a commit or range (computed in-page by the WASM engine); range views are clearly read-only.
  • Unified or side-by-side, plus a Wrap preference; split enables when the workspace is wide enough.
  • Export the review as lossless JSON, Markdown, or CSV from the Actions menu or summary drawer — and import JSON back (merge by comment id, newer edit wins).
  • PR-style diffs by default: merge-base(BASE, HEAD)..HEAD, so drift on the base branch doesn't pollute the review (--no-merge-base for the literal two-dot diff).
  • A PR description panel: commits authored by the notes author (PACKDIFF_SYSTEM_USER_EMAIL) are notes, not code — they are hidden from the page, and the PR-DESCRIPTION.md they committed renders as a commentable Description panel on top, like the pull request it will become.
  • Live progress: a progress bar with ETA at a terminal; in machine mode one { "Progress": ... } JSON line per second on stderr, ending with Done — agents always know the stage and what remains.
  • Script- and agent-native: piped stdout automatically switches to machine mode — exactly one two-space JSON document per run, success and failure alike, as single-key unions ({ "Packed": { … } }, { "UnknownRef": { … } }) — plus --dump-json for the full typed diff document and a documented exit-code table (packdiff help exitcodes).

How it works

The interesting part of the design: the data model is one Rust crate, compiled twice. The CLI links it natively to parse git output; every generated page carries it compiled to WebAssembly (~124 KB, base64-inlined, no wasm-bindgen, empty import object) as the in-browser comment engine. The page's JavaScript is a view layer only — validation, ordering, merge semantics, and export formats are defined once, in Rust, and run identically in tests, in the CLI, and in your browser.

That boundary is deliberate and permanent: strict Rust for the engine, vanilla JS for the player. The player (cli/assets/page.js) owns presentation and browser state — DOM, events, sidebar/wrap/theme/drafts, localStorage — and stays framework-free and build-step-free; anything that touches review semantics lives in Rust and reaches the page only through the WASM ABI (the stance, spelled out).

packdiff/
├── dto/      the data model (DTOs): schema types, unified-diff parser, comment
│             operations, exports — pure logic, no I/O, no clock
├── wasm/     thin C ABI over dto/, built for wasm32-unknown-unknown
├── cli/      the `packdiff` binary: git + HTML rendering; embeds the wasm
│   └── assets/  authored page CSS/JS (inlined at compile time)
├── docs/     reference documentation
└── tests/    wasm ABI tests + Playwright browser UI tests

Install

Prerequisites: stable Rust with the wasm32 target, and git.

$ rustup target add wasm32-unknown-unknown

Straight from this repository:

$ cargo install --git https://github.com/dkorolev/packdiff packdiff
$ packdiff --version
packdiff 0.2.4

From crates.io:

$ cargo install packdiff

From a local checkout:

$ git clone https://github.com/dkorolev/packdiff && cd packdiff
$ cargo build --release            # builds the wasm module too (cli/build.rs)
$ cargo install --path cli         # puts `packdiff` on your PATH

Publishing to crates.io is automated (maintainer note): merging a PR whose last commit bumps the workspace version publishes the crates in dependency order and tags the GitHub release. The full playbook, including the manual fallback and how the shared version is kept in one place, is in PUBLISHING.md.

To see it end to end without pointing at your own repo:

$ ./demo.sh
Wrote out/demo.html (2 files, +5 −2, 2 commits)
Open out/demo.html in a browser (file:// is fine). Click a diff line to comment.

Usage

packdiff <BASE> [HEAD] [OPTIONS]
packdiff <BASE>...<HEAD> [OPTIONS]     merge-base diff (the default semantics)
packdiff <BASE>..<HEAD> [OPTIONS]      literal two-dot diff

HEAD defaults to whatever is checked out, so the everyday form is just packdiff main. Git-style ranges work the way you expect: .. means the literal diff, ... the merge-base diff.

Option Meaning Default
-C, --repo Path to the git repository .
-o, --out Output HTML path; - for stdout packdiff-<base>-<head>.html
--context N Diff context lines 3
--no-merge-base Literal BASE..HEAD instead of PR semantics off
--title T Override the page title repo: BASE → HEAD
--dump-json PATH Also write the typed diff document as JSON off
--json Force machine mode (auto when stdout is not a terminal) auto
--open Open the generated page in the default browser off
--color MODE auto | always | never (NO_COLOR honored) auto
--verbose Echo git invocations and timing to stderr off

Humans at a terminal may use relaxed spellings (--no-color, bare version); the CLI resolves them and nudges toward the canonical form. Scripts must use canonical syntax — machine mode refuses aliases.

Exit codes: 0 success · 2 usage · 3 not a git repository · 4 unknown ref · 5 git/io failure · 130 interrupted — packdiff help exitcodes has the full table, and every machine-mode error carries a stage field.

Use as a library

The binary's two halves — extract a typed diff document from git, render it into the one-file page — are the crate's public Rust API:

let opts = packdiff::PackOptions::new(".", "main", "HEAD");
let out = packdiff::pack(&opts, &())?; // &(): no progress reporting
std::fs::write("review.html", &out.html)?;
[dependencies]
packdiff = { version = "0.2", default-features = false }

default-features = false drops the binary-only terminal machinery (indicatif). The build-time wasm prerequisite above still applies, and git must be on PATH at run time. The typed data model is re-exported as packdiff::dto; progress can be observed by implementing packdiff::progress::ProgressObserver (&() observes nothing). cargo run --example pack -- main runs the worked example, and consumers that only read packdiff's artifacts — exported comments, --dump-json documents — need just the pure-logic packdiff-dto crate.

Documentation

Doc Contents
docs/CLI.md Full CLI reference: options, exit codes, the --json contract, examples
docs/PAGE.md Using the review page: commenting, storage, exports/imports, carrying comments across regenerated diffs
docs/DATA-MODEL.md The schema spec: document shapes, validation, ordering, merge semantics, storage key, versioning
docs/WASM-ABI.md The WASM ABI: memory protocol, envelopes, every export, a minimal JS bridge
docs/ARCHITECTURE.md Crate boundaries, the wasm-embedding build pipeline, testing strategy
PUBLISHING.md Maintainer playbook: crate order, version reconciliation, the dry run, releasing to crates.io

Rust API docs: cargo doc -p packdiff -p packdiff-dto --open.

Tests and gates

$ ./test.sh

Runs cargo fmt --check, the dto unit tests, the CLI integration tests (driving the real binary against a scratch git repository, including the machine-mode contract), release-mode tests, and the Node tests exercising the actual compiled wasm module under the page's exact calling convention. A clean checkout stays green — tests missing a prerequisite skip with a hint.

The same pass gates pushes (git config core.hooksPath .githooks enables the pre-push hook) and merges (GitHub Actions CI) — see CONTRIBUTING.md.

Limitations (v0.1)

No syntax or word-level highlighting; the side-by-side view needs a wide-enough window; comments carry across regenerated diffs via export → import rather than automatically; localStorage is per-browser-profile by nature.

License

MIT — see LICENSE. © 2026 Dmitry "Dima" Korolev.