From da87970723302bc8690c4c108b8aac7013551021 Mon Sep 17 00:00:00 2001 From: Julien Calixte Date: Sat, 23 May 2026 22:32:28 +0000 Subject: [PATCH] docs: rewrite README and add ADR for the TUI choice Replace the generated Hex-package README with a usage-oriented one: behavior, requirements, exit codes, build. Add ADR-0001 documenting why this batch tool ships an interactive TUI (with the non-TTY fallback as part of the mitigation story). --- README.md | 56 ++++++++++++++++++++++++++++++++++++++++++-------------- docs/adr/0001-tui-for-a-batch-cli.md | 33 +++++++++++++++++++++++++++++++++ 2 file(s) changed, 75 insertion(s)(+), 14 deletion(s)(-) diff --git a/README.md b/README.md --- a/README.md +++ b/README.md @@ -1,24 +1,52 @@ # tinyimg -[![Package Version](https://img.shields.io/hexpm/v/tinyimg)](https://hex.pm/packages/tinyimg) -[![Hex Docs](https://img.shields.io/badge/hex-docs-ffaff3)](https://hexdocs.pm/tinyimg/) +Losslessly optimize PNG and JPG images in a directory, with a minimal terminal UI. ```sh -gleam add tinyimg@1 -``` -```gleam -import tinyimg - -pub fn main() -> Nil { - // TODO: An example of the project in use -} +tinyimg [DIR] ``` -Further documentation can be found at . +If `DIR` is omitted, the current working directory is used. Pixels are preserved exactly; metadata (EXIF, ICC, XMP) is stripped. Files are replaced atomically only when the optimized result is strictly smaller. -## Development +## Behavior + +- Recursive walk under `DIR`. Entries beginning with `.` are skipped during descent. +- Symlinks are not followed. +- If `DIR` is inside a git work tree, files matched by `.gitignore` are skipped (uses `git check-ignore`). +- One worker per logical CPU core; native tools forced single-threaded. +- Two-stage quit: `q` once stops scheduling and drains in-flight work; `q` again forces exit. +- TTY vs non-TTY: full TUI on a terminal, plain one-line-per-file streaming when piped. + +## Requirements + +Gleam (BEAM target) and at least one native optimizer per format you have: + +- PNG: `oxipng` (recommended) | `optipng` | `pngcrush` +- JPG: `jpegtran` (recommended) | `jpegoptim` + +On macOS with Homebrew: ```sh -gleam run # Run the project -gleam test # Run the tests +brew install oxipng jpeg-turbo # provides oxipng + jpegtran +# or +brew install optipng jpegoptim ``` + +## Exit codes + +| Code | Meaning | +|---|---| +| 0 | success (or cancelled with no failures) | +| 1 | one or more files failed, or run was cancelled | +| 2 | invalid usage or bad path | +| 3 | required native tool missing | + +## Build + +```sh +gleam run -- ./assets # try it +gleam test # run unit tests +gleam build # compile +``` + +See [`docs/adr/0001-tui-for-a-batch-cli.md`](docs/adr/0001-tui-for-a-batch-cli.md) for the design decision behind the TUI. diff --git a/docs/adr/0001-tui-for-a-batch-cli.md b/docs/adr/0001-tui-for-a-batch-cli.md new file mode 100644 --- /dev/null +++ b/docs/adr/0001-tui-for-a-batch-cli.md @@ -0,0 +1,33 @@ +# ADR 0001 — Use a shore TUI for a batch CLI + +**Status:** accepted + +## Context + +tinyimg is a batch image optimizer: point it at a directory, it optimizes every PNG/JPG inside. The natural shape for such a tool is a plain CLI that streams progress lines to stdout — that is what most batch tools (ffmpeg, imagemin, every CI optimizer) do. + +We chose instead to build an interactive Elm-Architecture TUI on top of [`shore`](https://hex.pm/packages/shore), with a progress bar, a running savings counter, and a tail of recent results. This is a surprising choice for a batch tool and worth recording so future readers don't undo it. + +## Decision + +For interactive runs (stdout is a TTY) we render a live TUI. For non-interactive runs (stdout piped, CI, redirected to a file) we fall back to plain one-line-per-file streaming with the same final summary block. + +The TUI's `Model` holds counters, a running total of saved bytes, and a bounded tail of the most recent `FileResult`s. Worker BEAM processes optimize files in parallel and send `WorkerFinished(FileResult)` messages into the shore actor's update loop. + +## Alternatives considered + +1. **Plain-text only.** Smallest possible CLI. Reject: no visibility into progress on large directories, no live "saved bytes so far" feedback. Users will run this on folders of hundreds of images and want to see motion. +2. **Spinner / single progress bar with no per-file output.** Less code than the TUI. Reject: when a file fails or is skipped, the user has no signal until the final summary; the most informative line — "which file is currently being processed and what's happening to it" — is missing. +3. **Make the TUI optional behind a flag.** Reject for v1: branching forces us to maintain two output paths anyway (one for TTY, one for piped), and adding a third (opt-in TUI) only multiplies surface area without changing the cases that matter. + +## Consequences + +- **+** Live progress, savings counter, and recent-results tail give the user a clear picture without dumping log lines to stdout. The alt-screen buffer means the user's shell scrollback isn't polluted. +- **+** Worker processes communicating via typed `Msg` variants is a natural fit for BEAM and lets us add features (cancellation, force-quit) without rearchitecting. +- **−** ~150 LoC of view code and an extra dependency (`shore`) we wouldn't need for a plain CLI. +- **−** Two output paths to maintain (TUI vs plain). Mitigated by the fact that both share the `Summary` accumulator and the same printing logic for the final block — the divergence is only in the live progress. +- **−** Shore writes terminal control sequences to stdout; if a user accidentally pipes our output without us detecting it, the result is garbage. Our `tinyimg_ffi:is_tty/0` check guards against this. + +## Reversibility + +Medium. Ripping out shore is mechanical (delete `src/tinyimg/tui.gleam` and the `shore` dep, route the TTY branch to `plain.run`), but every place we wired in a `Msg` variant or a model field gets touched. Don't undo this without a stronger reason than "TUI feels heavy." -- tangled.sh