From e67177e0d49a914895f3db580e2764e9c73ff580 Mon Sep 17 00:00:00 2001 From: Vyncint Ng <115854244+vyncint@users.noreply.github.com> Date: Wed, 16 Sep 2026 20:05:27 +0700 Subject: [PATCH] docs: link every changelog version, and name the parser in the spec MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CHANGELOG.md says it follows Keep a Changelog, whose format ends with a link reference definition per version. It had none, so every heading rendered as literal `[0.11.0]` brackets linking nowhere and "what actually changed between these two releases?" was zero clicks away instead of one. 0.4.2 compares against v0.4.0, not the v0.4.1 tag: 0.4.1 was tagged and never published, which its own entry records. docs/RELEASING.md now tells the next releaser to add a line, since a changelog convention that is not in the checklist decays on its first release. extract-changelog.sh slices the file by `## [` headings, so definitions at the foot fell inside the oldest release's section and would have been appended to v0.1.0's GitHub Release notes. It now stops at the first of them. Verified by extracting all nineteen sections before and after: byte identical. docs/DESIGN.md §3 is the normative spec for the snapshot text format and never mentioned `Screen::parse`, the function that reads it back — so someone implementing against the format went to the rustdoc to discover it round-trips at all. A new "Reading it back" subsection states what survives the trip and what does not, in the facts `Screen::parse`'s rustdoc already holds. Also defines `[`Unsupported`]`, which 0.11.0's entry referenced with no definition anywhere in the file. Closes #305 Closes #319 Signed-off-by: Vyncint Ng <115854244+vyncint@users.noreply.github.com> --- .github/scripts/extract-changelog.sh | 5 ++++ CHANGELOG.md | 35 ++++++++++++++++++++++++++++ docs/DESIGN.md | 34 +++++++++++++++++++++++++++ docs/RELEASING.md | 13 ++++++++++- 4 files changed, 86 insertions(+), 1 deletion(-) diff --git a/.github/scripts/extract-changelog.sh b/.github/scripts/extract-changelog.sh index a0caf3b..e73a34b 100755 --- a/.github/scripts/extract-changelog.sh +++ b/.github/scripts/extract-changelog.sh @@ -23,6 +23,11 @@ result="$(awk -v ver="$version" ' if (found) exit if (index($0, "[" ver "]") > 0) { found = 1; next } } + # Keep a Changelog ends with link reference definitions, which sit below + # the oldest version heading and therefore inside its section as far as + # the slice above is concerned. They belong to the file, not to any one + # release, so collecting stops at the first of them (#319). + found && /^\[[^]]+\]:[[:space:]]+http/ { exit } found { lines[++n] = $0 } END { start = 1; while (start <= n && lines[start] ~ /^[[:space:]]*$/) start++ diff --git a/CHANGELOG.md b/CHANGELOG.md index 4a44e60..d59556e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -53,6 +53,18 @@ reads that marker. could not (#312). A path that is not an existing directory is refused by name, in one line, with exit 2. +### Changed + +- Every version heading in this file now links to its compare view, as + [Keep a Changelog] specifies and this file's own header claimed (#319). + `[Unreleased]` compares the newest tag to `HEAD`. Release notes are + unaffected: `extract-changelog.sh` stops collecting at the definitions, + so the notes for every published version are byte-identical to before. + +- `docs/DESIGN.md` §3 now names `Screen::parse` and says what a round trip + through the text format preserves and what it does not (#305). The spec + had never mentioned its own reader. + ## [0.11.0] - 2026-09-11 ### Changed @@ -1801,3 +1813,26 @@ in the worst of them — hung itself. instant-exit caveat in `docs/DESIGN.md`) an output-loss race in the OS pty teardown, found by the stress workflow at roughly 1 in 80 instant-exit spawns on macOS. + +[Keep a Changelog]: https://keepachangelog.com/en/1.1.0/ +[`Unsupported`]: https://docs.rs/termlens/0.11.0/termlens/struct.Unsupported.html + +[Unreleased]: https://github.com/vyncint/termlens/compare/v0.11.0...HEAD +[0.11.0]: https://github.com/vyncint/termlens/compare/v0.10.3...v0.11.0 +[0.10.3]: https://github.com/vyncint/termlens/compare/v0.10.2...v0.10.3 +[0.10.2]: https://github.com/vyncint/termlens/compare/v0.10.1...v0.10.2 +[0.10.1]: https://github.com/vyncint/termlens/compare/v0.10.0...v0.10.1 +[0.10.0]: https://github.com/vyncint/termlens/compare/v0.9.0...v0.10.0 +[0.9.0]: https://github.com/vyncint/termlens/compare/v0.8.0...v0.9.0 +[0.8.0]: https://github.com/vyncint/termlens/compare/v0.7.0...v0.8.0 +[0.7.0]: https://github.com/vyncint/termlens/compare/v0.6.1...v0.7.0 +[0.6.1]: https://github.com/vyncint/termlens/compare/v0.6.0...v0.6.1 +[0.6.0]: https://github.com/vyncint/termlens/compare/v0.5.0...v0.6.0 +[0.5.0]: https://github.com/vyncint/termlens/compare/v0.4.2...v0.5.0 +[0.4.2]: https://github.com/vyncint/termlens/compare/v0.4.0...v0.4.2 +[0.4.0]: https://github.com/vyncint/termlens/compare/v0.3.0...v0.4.0 +[0.3.0]: https://github.com/vyncint/termlens/compare/v0.2.1...v0.3.0 +[0.2.1]: https://github.com/vyncint/termlens/compare/v0.2.0...v0.2.1 +[0.2.0]: https://github.com/vyncint/termlens/compare/v0.1.1...v0.2.0 +[0.1.1]: https://github.com/vyncint/termlens/compare/v0.1.0...v0.1.1 +[0.1.0]: https://github.com/vyncint/termlens/releases/tag/v0.1.0 diff --git a/docs/DESIGN.md b/docs/DESIGN.md index eb7f39f..3ca3d9a 100644 --- a/docs/DESIGN.md +++ b/docs/DESIGN.md @@ -682,6 +682,40 @@ The `insta` feature (default) re-exports `insta` and ships `assert_screen_snapshot!` so the snapshotting insta version can't drift from the one the macro targets. +### Reading it back + +`Screen::parse` (0.10.0) is the reader for everything above: it accepts +exactly what `Display` and `with_styles` write — the header, the grid, and +optionally the blank line, `styles:` and its spans — and every other line is +an `Error::Parse` naming it. The `termlens` CLI is built on it: `diff` and +`render` take saved screens, so a snapshot outlives the test run that +produced it. + +The round trip is exact **for what the format carries**. A parsed screen +renders to the same text, with the same `styles:` block, and `diff`s empty +against the original. It is not `==` to the original, and rule 5's last +paragraph is why: equality is "the same observation" and the text format is +deliberately blind to most of it. What does not survive is exactly what the +rules above never wrote down — an erased cell and a written blank are one +character either way; the out-of-band state, the title, the mode flags, the +counters and the history, comes back default; and a hidden cursor's position +is not recorded, so `cursor: hidden` parses back to a hidden cursor at +`0,0`. `diff` reports none of those as a difference, which is the same claim +from the other side: the text format is the *picture*, and two screens that +render alike are the same picture. + +The header's row count is what decides where the grid ends — not a search +for the `styles:` marker — so a grid may hold the words `styles:` or +`(none)` as ordinary content. The one ambiguous input is a snapshot whose +trailing blank rows were trimmed by hand *and* which carries a styles block: +its block falls inside the declared row count and is read as content. +Content wins on purpose, because a wrong row of text shows up in a diff and +a silently dropped one does not. + +The JSON shape below is the format for the other job — carrying a `Screen` +whole, out-of-band state included, so that reading it back gives one that +compares `==`. + ### What a reader skips, and what it does not The format proper begins at the `size:` header and ends with the grid or diff --git a/docs/RELEASING.md b/docs/RELEASING.md index 4c10ec2..35766e7 100644 --- a/docs/RELEASING.md +++ b/docs/RELEASING.md @@ -76,9 +76,20 @@ $EDITOR Cargo.toml crates/termlens-cli/Cargo.toml cargo check --workspace # refreshes Cargo.lock # 2. Move the CHANGELOG section. A `- **Breaking:**` bullet moves with it, -# which is what lets the tag's semver run pass (see above). +# which is what lets the tag's semver run pass (see above). Then add the +# new version's link definition at the foot of the file and repoint +# [Unreleased] at the tag about to be cut — a Keep a Changelog convention +# that is not in this checklist decays on its first release (#319): +# +# [Unreleased]: …/compare/vX.Y.Z...HEAD <- was vPREV...HEAD +# [X.Y.Z]: …/compare/vPREV...vX.Y.Z <- new line, under it +# $EDITOR CHANGELOG.md # [Unreleased] -> [X.Y.Z] - YYYY-MM-DD # add a fresh empty [Unreleased] above + # and two lines at the foot +# Release notes stop above the link definitions, so they are unchanged by +# this; `extract-changelog.sh X.Y.Z` prints exactly the section. +.github/scripts/extract-changelog.sh X.Y.Z # what the GitHub Release will say # 2b. Freeze this release's saved-screen shapes into the compatibility # corpus (crates/termlens/tests/compat/README.md), from this tree: