From 5053e4c93207f60b4bc8854ff642bc2c56eb8998 Mon Sep 17 00:00:00 2001
From: PerchAnton <101743951+PerchAnton@users.noreply.github.com>
Date: Tue, 27 Oct 2026 08:26:37 +0200
Subject: [PATCH] Demonstrate stale drafts after Undo with a verified context
diagram
---
.github/workflows/ci.yml | 2 +
CHANGELOG.md | 9 ++
Cargo.toml | 2 +-
PACKAGE.md | 1 +
README.md | 46 ++++++++-
docs/CONTEXT_WALKTHROUGH.md | 83 +++++++++++++++++
docs/assets/README.md | 16 ++++
docs/assets/context-walkthrough.svg | 38 ++++++++
docs/evidence/context-walkthrough.json | 46 +++++++++
examples/context_walkthrough.rs | 123 +++++++++++++++++++++++++
tools/context_evidence.py | 76 +++++++++++++++
11 files changed, 437 insertions(+), 5 deletions(-)
create mode 100644 docs/CONTEXT_WALKTHROUGH.md
create mode 100644 docs/assets/context-walkthrough.svg
create mode 100644 docs/evidence/context-walkthrough.json
create mode 100644 examples/context_walkthrough.rs
create mode 100644 tools/context_evidence.py
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index efb0167..879773a 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -13,6 +13,8 @@ jobs:
- run: rustup toolchain install ${{ matrix.rust }} --profile minimal --component rustfmt,clippy
- run: cargo +${{ matrix.rust }} test --locked
- run: cargo +${{ matrix.rust }} run --locked --example retry_command
+ - run: cargo +${{ matrix.rust }} run --locked --example context_walkthrough
+ - run: python3 tools/context_evidence.py --check
- run: cargo +${{ matrix.rust }} fmt --check
- run: cargo +${{ matrix.rust }} clippy --locked --all-targets -- -D warnings
- run: cargo +${{ matrix.rust }} publish --dry-run --locked --registry crates-io
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 17e891f..15674c1 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,14 @@
# Changelog
+## Unreleased
+
+- Add a runnable context walkthrough: a host edit followed by Undo restores the
+ value but invalidates an old draft; Reset does not rebase its captured context.
+- Generate a front-page diagram from the example's output and source hashes;
+ verify both on Rust 1.88 and stable in CI. Include the renderer in source packages.
+- Put concrete behavior, local-helper trade-offs and host responsibilities first in
+ the README. Public API, runtime behavior and published versions are unchanged.
+
## 0.1.0-alpha.4
Published to [crates.io](https://crates.io/crates/edit-session/0.1.0-alpha.4) on
diff --git a/Cargo.toml b/Cargo.toml
index 1af6e77..0a59c26 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -10,7 +10,7 @@ readme = "PACKAGE.md"
publish = ["crates-io"]
keywords = ["editing", "draft", "validation", "undo", "no-std"]
categories = ["no-std", "gui"]
-include = ["src/**", "tests/**", "examples/**", "docs/**", "README.md", "PACKAGE.md", "CHANGELOG.md", "LICENSE-*", "Cargo.toml", "Cargo.lock"]
+include = ["src/**", "tests/**", "examples/**", "docs/**", "tools/context_evidence.py", "README.md", "PACKAGE.md", "CHANGELOG.md", "LICENSE-*", "Cargo.toml", "Cargo.lock"]
[[example]]
name = "retry_command"
diff --git a/PACKAGE.md b/PACKAGE.md
index 39f85ca..ee2d572 100644
--- a/PACKAGE.md
+++ b/PACKAGE.md
@@ -45,6 +45,7 @@ must retain the draft for retry, use the borrowed-preview/check pattern in the
Check the live value and context again on every attempt.
- [API source and contracts](https://github.com/PerchAnton/edit-session/blob/main/src/lib.rs)
+- [Runnable example: Undo restores a value but leaves an old draft stale](https://github.com/PerchAnton/edit-session/blob/main/docs/CONTEXT_WALKTHROUGH.md)
- [Changelog](https://github.com/PerchAnton/edit-session/blob/main/CHANGELOG.md)
- [Integration evidence and limits](https://github.com/PerchAnton/edit-session/blob/main/docs/ADOPTION.md)
- [Report a bug](https://github.com/PerchAnton/edit-session/issues/new?template=bug_report.yml)
diff --git a/README.md b/README.md
index 816900b..da6e2bc 100644
--- a/README.md
+++ b/README.md
@@ -1,12 +1,45 @@
# edit-session
-Draft editing with document, target and revision checks. A standalone Rust library;
-the separate [interaction-check](https://github.com/PerchAnton/interaction-check) tool is
-an optional way to test consumer workflows, not a dependency of this crate.
+Keep an editable draft separate from live data, then check that its document, target
+and revision are still current before the host applies a change. A standalone Rust
+library: `no_std`, zero runtime dependencies, Rust 1.88+.
[](https://github.com/PerchAnton/edit-session/actions/workflows/ci.yml)
-**[Download](#current-prerelease)** · **[Try recovery](#retry-after-a-rejected-command)** · **[Use the library](#use-from-another-project)** · **[Evidence](#evidence-and-trade-offs)** · **[License](#license)**
+**[First example](#same-value-different-edit)** · **[Try recovery](#retry-after-a-rejected-command)** · **[Install](#use-from-another-project)** · **[Evidence](#evidence-and-trade-offs)** · **[License](#license)**
+
+## Same value, different edit
+
+
+
+From a checkout of this repository, run:
+
+```sh
+cargo run --locked --example context_walkthrough
+```
+
+The example prints four states and asserts the actual library results. A value-only
+comparison would accept the restored value after Undo. The host-supplied revision
+still differs, so `check` and `finish` return `ContextChanged`. **Reset changes the
+preview; it does not rebase an old draft onto the current document.**
+
+This is an original synchronous teaching model, verified **27 October 2026**, not a
+new application regression or a benchmark. The diagram is generated from its output
+and checked in CI. [Walkthrough and host responsibilities](docs/CONTEXT_WALKTHROUGH.md)
+· [Example source](examples/context_walkthrough.rs) · [Raw evidence](docs/evidence/context-walkthrough.json).
+
+| Your situation | Reasonable choice |
+| --- | --- |
+| A small dialog already has correct draft logic | Keep the local helper unless a concrete gap justifies a dependency |
+| Several consumers need the same draft validity contract | Evaluate this library against their native regression tests |
+| The host command can fail and the user must retry | Use the [borrowed-preview recovery pattern](docs/RECOVERY.md); retain fields until the command succeeds |
+| You need widgets, rendering, Undo history or transaction rollback | Those remain responsibilities of the host application |
+
+The host supplies stable identities, advances revisions, and keeps the check and
+command on the same serialized edit path. A correct local helper can provide the
+same protection; this example does not claim less code or faster execution.
+The separate [interaction-check](https://github.com/PerchAnton/interaction-check)
+development tool is optional. Neither project depends on the other.
## Current prerelease
@@ -14,6 +47,9 @@ an optional way to test consumer workflows, not a dependency of this crate.
**[API documentation](https://docs.rs/edit-session/0.1.0-alpha.4/edit_session/)** · [Download the crate](https://crates.io/api/v1/crates/edit-session/0.1.0-alpha.4/download) · [SHA-256 and installation evidence](docs/evidence/release-alpha4.json) · [Changelog](CHANGELOG.md) · [Use as a dependency](#use-from-another-project)
+
+Published package snapshot and verification
+

Release snapshot: **16 lifecycle tests + 8 host-example tests + 1 doctest**,
@@ -28,6 +64,8 @@ tracks current main, while release facts stay tied to the tagged commit. The
[previous GitHub release](https://github.com/PerchAnton/edit-session/releases/tag/v0.1.0-alpha.3)
remains available.
+
+
Trying the alpha? [Report a bug](https://github.com/PerchAnton/edit-session/issues/new?template=bug_report.yml)
with your revision and a short sequence of calls, or read the [alpha compatibility policy](docs/RELEASING.md#alpha-compatibility).
diff --git a/docs/CONTEXT_WALKTHROUGH.md b/docs/CONTEXT_WALKTHROUGH.md
new file mode 100644
index 0000000..ebc12e6
--- /dev/null
+++ b/docs/CONTEXT_WALKTHROUGH.md
@@ -0,0 +1,83 @@
+# Same value, different edit
+
+An open draft remembers the value and context from when editing began. If another
+action changes the document and Undo restores the original value, the old draft is
+still based on an earlier revision. Comparing only values misses that history.
+
+Run the [complete Rust example](../examples/context_walkthrough.rs) from a checkout:
+
+```sh
+cargo run --locked --example context_walkthrough
+```
+
+It calls the real library, uses no third-party dependencies, and checks its results
+with assertions. It is an original synchronous teaching model, not a GUI test or a
+newly discovered bug in an application. Rust 1.88+ is sufficient.
+
+| Action | Live value | Host revision | Draft preview | `check` |
+| --- | ---: | ---: | ---: | --- |
+| Open draft, propose 75 | 100 | 42 | 75 | `Ok(())` |
+| Another host edit | 120 | 43 | 75 | `ContextChanged` |
+| Host Undo | 100 | 44 | 75 | `ContextChanged` |
+| Reset the old draft | 100 | 44 | 100 | `ContextChanged` |
+
+The original revision remains 42. `finish` returns `ContextChanged`, including after
+Reset. The host must reject or dismiss this stale draft rather than apply it to the
+newer state. A deliberate reload/reopen can start a **new** session; Reset cannot.
+
+
+
+## What the application must supply
+
+The library does not observe edits or increment revisions. The application supplies
+stable document and target identities, and a revision that changes on every relevant
+edit, Undo and Redo. A wrapping or reused revision can make a different state appear
+current; choose a counter/identity scheme that avoids reuse during a session's lifetime.
+
+Use owned snapshots or immutable shared values. Cloning a pointer to shared mutable
+storage does not isolate the draft. Equality must cover all relevant fields.
+
+Freshness checks and command execution must share the application's serialized edit
+path. A successful check followed by a separately scheduled write is not protected
+from intervening changes. The library provides no lock or transaction rollback.
+
+For a host command that may reject an edit, use the existing
+[retry example](RECOVERY.md): check, borrow the preview, try the atomic host command,
+and close only after success. Retain raw input on rejection. Calling `finish` first
+consumes the session and is not a retry strategy.
+
+## Why use a library instead of a small struct?
+
+A correct local helper can implement the same checks. This example demonstrates the
+contract, not code savings or an advantage over that helper. The reusable part is one
+tested implementation of draft validity, original/preview values and context checks.
+It is worth a dependency only when consumers need that shared contract.
+
+| Existing situation | Reasonable choice |
+| --- | --- |
+| A simple dialog with working local draft logic | Keep the local helper unless a concrete gap is demonstrated |
+| Several consumers need the same draft contract | Evaluate edit-session against the same native regression tests |
+| Preview, workspace reload or backup already solves the need | Keep that workflow; a new dependency needs a separate reason |
+| A useful regression can be expressed as a native Rust test | Use that test; interaction-check is optional |
+| A renderer, Undo engine, asynchronous transaction or conflict merge is missing | Solve that in the host; this library does not provide it |
+
+These choices reflect [Oculante, SSBH Editor and Tarsier feedback](ADOPTION.md#what-feedback-changes-our-approach).
+The [PdfCraft evidence](ADOPTION.md#a-demonstrated-failure-in-a-real-consumer) concerns a
+complete proposed consumer patch, including host changes. It does not prove that an
+external crate is preferable to a local helper, and neither integration is adopted.
+
+## Reproduce the diagram
+
+```sh
+cargo build --locked --example context_walkthrough
+python tools/context_evidence.py
+python tools/context_evidence.py --check
+```
+
+Python is used only to render/verify the repository graphic; using the crate and
+running its Rust examples do not require it. Pass `--binary PATH` if Cargo uses a
+custom target directory. The script runs the example's `--json` mode, records its
+output and LF-normalized source hashes in [the snapshot](evidence/context-walkthrough.json),
+and renders the SVG from those values. CI checks both on Rust 1.88 and stable.
+These walkthrough assertions are separate from the existing 16 lifecycle tests,
+8 recovery tests and one compile-fail doctest; they are not new upstream regressions.
diff --git a/docs/assets/README.md b/docs/assets/README.md
index d40ea5e..e0e28e9 100644
--- a/docs/assets/README.md
+++ b/docs/assets/README.md
@@ -1,5 +1,21 @@
# README overview asset
+## Executable context walkthrough
+
+`context-walkthrough.svg` is generated from the real output of
+[the Rust example](../../examples/context_walkthrough.rs), recorded on 27 October
+2026 in [JSON](../evidence/context-walkthrough.json). The example asserts that a host
+edit, Undo and Reset leave an old draft stale. It is a synchronous teaching model,
+not an additional application regression or a performance measurement.
+
+Build with `cargo build --locked --example context_walkthrough`, then run
+`python tools/context_evidence.py` from the repository root. Add `--check` to verify
+the saved output, LF-normalized source hashes and SVG without rewriting them.
+The existing Rust 1.88/stable CI matrix runs this check. See the
+[walkthrough](../CONTEXT_WALKTHROUGH.md) for responsibilities and reproduction.
+
+## Earlier measured overview
+
`overview.svg` is an editable, self-contained vector graphic. It uses system fonts,
explicit light-background colors, a title and a description, with no scripts, external
fonts, images or trackers. The repository README repeats its measurements as accessible
diff --git a/docs/assets/context-walkthrough.svg b/docs/assets/context-walkthrough.svg
new file mode 100644
index 0000000..58a8a6a
--- /dev/null
+++ b/docs/assets/context-walkthrough.svg
@@ -0,0 +1,38 @@
+
diff --git a/docs/evidence/context-walkthrough.json b/docs/evidence/context-walkthrough.json
new file mode 100644
index 0000000..b471128
--- /dev/null
+++ b/docs/evidence/context-walkthrough.json
@@ -0,0 +1,46 @@
+{
+ "recorded_date": "2026-10-27",
+ "scope": "Original synchronous teaching model; actual edit-session API, no GUI or upstream test claim",
+ "source_hash_format": "SHA-256 of UTF-8 text with LF line endings",
+ "source_files": {
+ "src/lib.rs": "878d877fc7c8874230c93aafd8321613b3c02b4dcf94865f2629e21fa088a74c",
+ "examples/context_walkthrough.rs": "4a8fd1642ba38900d757e010e2ce6287e5272c296207a7381e0e4d24e95c307c"
+ },
+ "result": {
+ "rows": [
+ {
+ "action": "Open draft",
+ "live": 100,
+ "revision": 42,
+ "preview": 75,
+ "value_matches_original": true,
+ "check": "Ready"
+ },
+ {
+ "action": "Host edits",
+ "live": 120,
+ "revision": 43,
+ "preview": 75,
+ "value_matches_original": false,
+ "check": "ContextChanged"
+ },
+ {
+ "action": "Host undoes",
+ "live": 100,
+ "revision": 44,
+ "preview": 75,
+ "value_matches_original": true,
+ "check": "ContextChanged"
+ },
+ {
+ "action": "Reset draft",
+ "live": 100,
+ "revision": 44,
+ "preview": 100,
+ "value_matches_original": true,
+ "check": "ContextChanged"
+ }
+ ],
+ "finish": "ContextChanged"
+ }
+}
diff --git a/examples/context_walkthrough.rs b/examples/context_walkthrough.rs
new file mode 100644
index 0000000..2d2b6ab
--- /dev/null
+++ b/examples/context_walkthrough.rs
@@ -0,0 +1,123 @@
+//! Why an edit followed by Undo still invalidates an older draft.
+//! An original synchronous teaching model, not an application integration test.
+use edit_session::{EditSession, FinishError};
+
+#[derive(Clone, Copy, PartialEq, Eq)]
+struct Context {
+ document: u64,
+ target: u64,
+ revision: u64,
+}
+
+struct Row {
+ action: &'static str,
+ live: u8,
+ revision: u64,
+ preview: u8,
+ value_matches_original: bool,
+ check: &'static str,
+}
+
+fn observe(
+ action: &'static str,
+ live: u8,
+ context: Context,
+ draft: &EditSession,
+) -> Row {
+ let check = match draft.check(&live, &context) {
+ Ok(()) => "Ready",
+ Err(FinishError::ContextChanged) => "ContextChanged",
+ Err(FinishError::ValueChanged) => "ValueChanged",
+ Err(FinishError::InvalidProposal) => "InvalidProposal",
+ };
+ Row {
+ action,
+ live,
+ revision: context.revision,
+ preview: *draft.preview(),
+ value_matches_original: live == *draft.original(),
+ check,
+ }
+}
+
+fn main() {
+ let args: Vec<_> = std::env::args().skip(1).collect();
+ let json = match args.as_slice() {
+ [] => false,
+ [flag] if flag == "--json" => true,
+ _ => {
+ eprintln!("Usage: context_walkthrough [--json]");
+ std::process::exit(2);
+ }
+ };
+ let mut live = 100_u8;
+ let mut context = Context {
+ document: 7,
+ target: 2,
+ revision: 42,
+ };
+ let mut draft = EditSession::new(live, context);
+ assert_eq!(draft.propose::<&str>(Ok(75)), Ok(()));
+ let mut rows = vec![observe("Open draft", live, context, &draft)];
+ assert_eq!(draft.check(&live, &context), Ok(()));
+
+ // The host owns both the external edit and its monotonic revision.
+ live = 120;
+ context.revision += 1;
+ rows.push(observe("Host edits", live, context, &draft));
+ assert_eq!(
+ draft.check(&live, &context),
+ Err(FinishError::ContextChanged)
+ );
+
+ // Undo restores the VALUE, but must advance the host's revision again.
+ live = 100;
+ context.revision += 1;
+ rows.push(observe("Host undoes", live, context, &draft));
+ assert_eq!(live, *draft.original());
+ assert_eq!(
+ draft.check(&live, &context),
+ Err(FinishError::ContextChanged)
+ );
+
+ // Reset changes the preview, never the captured document/target/revision.
+ draft.reset();
+ rows.push(observe("Reset draft", live, context, &draft));
+ assert_eq!(*draft.preview(), live);
+ assert_eq!(draft.context().revision, 42);
+ assert_eq!(
+ draft.finish(&live, &context),
+ Err(FinishError::ContextChanged)
+ );
+
+ if json {
+ println!("{{\"rows\":[");
+ for (i, row) in rows.iter().enumerate() {
+ println!(
+ "{{\"action\":\"{}\",\"live\":{},\"revision\":{},\"preview\":{},\"value_matches_original\":{},\"check\":\"{}\"}}{}",
+ row.action,
+ row.live,
+ row.revision,
+ row.preview,
+ row.value_matches_original,
+ row.check,
+ if i + 1 == rows.len() { "" } else { "," }
+ );
+ }
+ println!("],\"finish\":\"ContextChanged\"}}");
+ } else {
+ println!("Action | Live | Revision | Preview | Value equals original | check()");
+ for row in &rows {
+ println!(
+ "{:<13} | {:>4} | {:>8} | {:>7} | {:<21} | {}",
+ row.action,
+ row.live,
+ row.revision,
+ row.preview,
+ row.value_matches_original,
+ row.check
+ );
+ }
+ println!("finish(): ContextChanged. The host must not apply the old draft.");
+ }
+}
diff --git a/tools/context_evidence.py b/tools/context_evidence.py
new file mode 100644
index 0000000..e4d02d3
--- /dev/null
+++ b/tools/context_evidence.py
@@ -0,0 +1,76 @@
+"""Run the Rust walkthrough and keep its README diagram tied to real output."""
+import argparse
+from datetime import datetime, timezone
+import hashlib
+from html import escape
+import json
+from pathlib import Path
+import subprocess
+
+ROOT = Path(__file__).resolve().parents[1]
+REPORT = ROOT / "docs/evidence/context-walkthrough.json"
+SVG = ROOT / "docs/assets/context-walkthrough.svg"
+
+
+def source_hashes():
+ return {name: hashlib.sha256((ROOT / name).read_bytes().replace(b"\r\n", b"\n")).hexdigest()
+ for name in ("src/lib.rs", "examples/context_walkthrough.rs")}
+
+
+def render(data):
+ cards = []
+ for index, row in enumerate(data["result"]["rows"]):
+ x = 32 + index * 248
+ ready = row["check"] == "Ready"
+ color = "#087c79" if ready else "#9a461b"
+ background = "#eaf6f3" if ready else "#fff1e9"
+ cards.append(f'''
+
+{index+1}. {escape(row['action'])}
+LIVE VALUE
+{row['live']}
+Revision {row['revision']} · preview {row['preview']}
+{row['check']}''')
+ return f'''
+'''
+
+
+def main():
+ parser = argparse.ArgumentParser(description=__doc__)
+ parser.add_argument("--binary", type=Path, default=ROOT / "target/debug/examples/context_walkthrough")
+ parser.add_argument("--check", action="store_true")
+ args = parser.parse_args()
+ completed = subprocess.run([str(args.binary.resolve()), "--json"], text=True,
+ capture_output=True, timeout=15, check=True)
+ result = json.loads(completed.stdout)
+ if args.check:
+ data = json.loads(REPORT.read_text(encoding="utf-8"))
+ if data["result"] != result or data["source_files"] != source_hashes():
+ raise SystemExit("Walkthrough changed; run this script without --check")
+ if SVG.read_text(encoding="utf-8") != render(data):
+ raise SystemExit("Diagram changed; run this script without --check")
+ else:
+ data = {"recorded_date": datetime.now(timezone.utc).date().isoformat(),
+ "scope": "Original synchronous teaching model; actual edit-session API, no GUI or upstream test claim",
+ "source_hash_format": "SHA-256 of UTF-8 text with LF line endings",
+ "source_files": source_hashes(), "result": result}
+ REPORT.write_text(json.dumps(data, indent=2) + "\n", encoding="utf-8")
+ SVG.write_text(render(data), encoding="utf-8")
+ print("Context walkthrough output, source hashes and diagram verified")
+
+
+if __name__ == "__main__":
+ main()