Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .github/workflows/evidence.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,16 @@ on: [push, pull_request]
permissions:
contents: read
jobs:
dispatch:
runs-on: ubuntu-24.04
timeout-minutes: 5
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
- uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0
with:
python-version: '3.14'
- run: python -m pip install -r examples/dispatch/requirements.txt
- run: python examples/dispatch/verify.py --check
pyee-native:
runs-on: ubuntu-24.04
timeout-minutes: 10
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ __pycache__/
*.pyc
.venv/
.venv-pyee/
.venv-dispatch/
**/target/
/.artifacts/
**/node_modules/
Expand Down
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
# Changelog

## Unreleased

- Add a focused python-dispatch example with native tests, optional expected/observed
Mermaid diagrams and tables that separate property writes, new callbacks, cumulative
log and the property value. Demonstrate the first mismatch with a wrong expectation.
- Explain the existing rat-salsa case through five real API calls, adapter costs and
UI limits; link the existing pyee native-test PR without duplicating it.
- Runner behavior, protocol, CLI options and runtime dependencies are unchanged.

## 0.1.0a7 / v0.1.0-alpha.7

Published on 27 October 2026 (Europe/Kyiv) to [PyPI](https://pypi.org/project/interaction-check/0.1.0a7/)
Expand Down
2 changes: 2 additions & 0 deletions MANIFEST.in
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,5 @@ include docs/NATIVE_TESTS.md docs/RELEASING.md
recursive-include examples/quickstart *.py *.json *.md
include docs/CHOOSING.md docs/REPORTING.md
include tools/render_quickstart.py docs/evidence/quickstart.json docs/assets/quickstart.svg
recursive-include examples/dispatch *.py *.json *.md *.txt
include docs/REAL_API_WALKTHROUGH.md docs/evidence/dispatch.json
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,14 @@ checked prefix, which must be rerun; it does not automatically minimize a failur
[Choosing a tool](docs/CHOOSING.md) connects these decisions to maintainer feedback.
[A short useful bug report](docs/REPORTING.md) shows how to share one actionable failure.

## Read a real API trace

Current source includes **[property writes and callbacks](examples/dispatch/README.md)**,
[expected/observed Mermaid diagrams](examples/dispatch/trace.md), a
[deliberate first mismatch](examples/dispatch/mismatch.md), and ordinary native tests.
For terminal widgets, see [five rat-salsa calls and their limits](docs/REAL_API_WALKTHROUGH.md).
These examples are newer than the published package; the runner protocol is unchanged.

## Current prerelease

**[0.1.0a7 on PyPI](https://pypi.org/project/interaction-check/0.1.0a7/) · published 27 October 2026 (Europe/Kyiv) · Python 3.10+**
Expand Down
5 changes: 5 additions & 0 deletions docs/CHOOSING.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,11 @@ The two projects are independent:

## What the feedback changed

For a real-library walkthrough and expected/observed diagrams, see
[python-dispatch](../examples/dispatch/README.md). For the precise scope of the terminal
widget proposal, see [rat-salsa](REAL_API_WALKTHROUGH.md). The pyee native scenario is
already in [PR #228](https://github.com/jfhbrook/pyee/pull/228); no duplicate is needed.

| Question from maintainers | Concrete response in this repository |
| --- | --- |
| [Redux: what problem and upstream change?](https://github.com/reduxjs/redux/discussions/4962#discussioncomment-19203020) | Use the decision table above. Current core-store examples justify no change to Redux's CI. |
Expand Down
43 changes: 43 additions & 0 deletions docs/REAL_API_WALKTHROUGH.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# What an adapter does: rat-salsa

The [rat-salsa question](https://github.com/thscharler/rat-salsa/issues/36#issuecomment-6461513249)
was about what we proposed: an optional external reproduction maintained by us.
It does not justify an upstream dependency or CI change.

The existing [five-step case](https://github.com/PerchAnton/interaction-check/blob/055dfc24a4eda7478719dcbbb1f0ac123176aec9/scenarios/rat-salsa/03-empty-noop-redo.json)
calls a real `rat_text::text_input::TextInputState` through this
[Rust adapter](https://github.com/PerchAnton/interaction-check/blob/055dfc24a4eda7478719dcbbb1f0ac123176aec9/adapters/rat-salsa/src/main.rs):

| JSON event | Actual API | Expected `value` |
| --- | --- | --- |
| `{"op":"new","text":""}` | `TextInputState::new()`, `set_text("")` | `""` |
| `{"op":"text","text":"x"}` | `insert_str("x")` | `"x"` |
| `{"op":"key","key":"undo"}` | `undo()` | `""` |
| `{"op":"key","key":"backspace"}` | `delete_prev_char()` | `""` |
| `{"op":"key","key":"redo"}` | `redo()` | `"x"` |

The adapter returns `buffer.text()` after each action, alongside cursor/selection.
A hypothetical failure on the last step would report `state.value: expected 'x', got ''`.
This illustrates the report shape, **not a newly reproduced rat-salsa bug**. The adapter
and lockfile are on the linked historical evaluation revision; PyPI does not install them.

For a current-source example that you can run and see fail deliberately, use
[JSON → python-dispatch → callbacks](../examples/dispatch/README.md). Its verifier
checks the first mismatch and produces expected/observed diagrams without a Rust toolchain.

## Why not a normal Rust test?

A native test can call those same five methods and assert `buffer.text() == "x"`.
That is preferred for a single regression. Replay adds Python, a JSONL adapter,
scenario maintenance and startup/serialization cost. It helps only if sharing a case
**within that adapter's contract** makes reproduction or revision comparison easier.

The methods do not exercise terminal rendering, focus routing, OS keyboard events,
clipboard or an application's integration. They establish neither additional coverage
nor faster tests. All actions may already have run before the first mismatch is reported;
exporting a checked prefix does not undo effects.

For pyee, the proposed behavior is already in the existing
[native-test PR #228](https://github.com/jfhbrook/pyee/pull/228). A new runner dependency
and a duplicate PR are unnecessary. Acceptance and adoption remain separate from
passing external examples.
207 changes: 207 additions & 0 deletions docs/evidence/dispatch.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
{
"package": "python-dispatch==0.2.3",
"native_tests": 3,
"synthetic_wrong_expectation": true,
"source_sha256": {
"examples/dispatch/adapter.py": "f986bea18171e9d875b2e6ed9c706a3f9a425261abb3c1bbd1a5ed9b3e0b41dd",
"examples/dispatch/native_tests.py": "50c0b900ead484bbbc7171e15385339c3eb5c14a5e267c3fb15e6e743b60db79",
"examples/dispatch/scenario.json": "84c731627683dd79093f97c6a3244c0ac4d539c97bcdf5d638b97e10155c2f07",
"examples/dispatch/render.py": "c3897660a53ee8a2505318032db25521951bcabe1201910439f40e1da5347f15",
"examples/dispatch/verify.py": "8a138d1ce86697f8a72a58db166affce1c4b5dbcfd03a70a4540b202a93bb125",
"examples/dispatch/requirements.txt": "485695df5b9bcd20665e0a74d7619dad44c469004de763390c5752ff249bd91c"
},
"results": [
{
"name": "Property writes are not callback counts",
"passed": true,
"steps": 5,
"stderr": "",
"trace": [
{
"step": 1,
"event": {
"op": "write",
"values": [
1,
1
],
"locked": false
},
"expect": {
"count": 1,
"log": [
1
]
},
"actual": {
"count": 1,
"log": [
1
]
},
"passed": true
},
{
"step": 2,
"event": {
"op": "write",
"values": [
2,
3,
4
],
"locked": true
},
"expect": {
"count": 4,
"log": [
1,
4
]
},
"actual": {
"count": 4,
"log": [
1,
4
]
},
"passed": true
},
{
"step": 3,
"event": {
"op": "write",
"values": [
4
],
"locked": false
},
"expect": {
"count": 4,
"log": [
1,
4
]
},
"actual": {
"count": 4,
"log": [
1,
4
]
},
"passed": true
},
{
"step": 4,
"event": {
"op": "unbind"
},
"expect": {
"count": 4,
"log": [
1,
4
]
},
"actual": {
"count": 4,
"log": [
1,
4
]
},
"passed": true
},
{
"step": 5,
"event": {
"op": "write",
"values": [
5
],
"locked": false
},
"expect": {
"count": 5,
"log": [
1,
4
]
},
"actual": {
"count": 5,
"log": [
1,
4
]
},
"passed": true
}
]
},
{
"passed": false,
"error": "Step 2 ({'op': 'write', 'values': [2, 3, 4], 'locked': True}): state.log: expected [1, 2, 3, 4], got [1, 4]",
"trace": [
{
"step": 1,
"event": {
"op": "write",
"values": [
1,
1
],
"locked": false
},
"expect": {
"count": 1,
"log": [
1
]
},
"actual": {
"count": 1,
"log": [
1
]
},
"passed": true
},
{
"step": 2,
"event": {
"op": "write",
"values": [
2,
3,
4
],
"locked": true
},
"expect": {
"count": 4,
"log": [
1,
2,
3,
4
]
},
"actual": {
"count": 4,
"log": [
1,
4
]
},
"passed": false,
"differences": [
"state.log: expected [1, 2, 3, 4], got [1, 4]"
]
}
]
}
]
}
74 changes: 74 additions & 0 deletions examples/dispatch/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
# From JSON to real property callbacks

This example calls **python-dispatch 0.2.3**, not a simulation. It is external
evaluation code maintained here, not an upstream integration or a discovered bug.
From current source (these files are newer than the alpha.7 tag), in a virtual environment:

```sh
python -m pip install -r examples/dispatch/requirements.txt
python examples/dispatch/verify.py --check
```

The command runs 3 ordinary tests, 5 replay steps, and a deliberately incorrect
expectation that must fail at step 2. The extra package is for this example only;
the runner still has no runtime dependencies.

| Term | Meaning |
| --- | --- |
| Write | Assignment to `model.count`; an equal assignment may emit nothing |
| Callback | One invocation of the retained receiver |
| New callbacks | The suffix added to `log` during this step |
| `log` | All callback values since the adapter started, in order |
| `count` | Current **property value**, not the number of callbacks |

The [complete trace](trace.md) has separate expected/observed tables and Mermaid
diagrams. At step 2, three locked writes `[2, 3, 4]` produce one new callback `[4]`;
the cumulative log is `[1, 4]`. After unbinding, writing `5` changes `count` but leaves
the log unchanged. See the library's [property API](https://python-dispatch.readthedocs.io/en/latest/properties.html).

## JSON → adapter → actual API → comparison

```json
{"event": {"op": "write", "values": [2, 3, 4], "locked": true},
"expect": {"count": 4, "log": [1, 4]}}
```

1. The runner sends only `event`, a protocol number and a step ID to [adapter.py](adapter.py).
2. The adapter opens `model.emission_lock("count")` and assigns each value to a real `Property`.
3. The receiver appends observed notifications. The adapter returns `count` and `log`.
4. The runner compares that snapshot with `expect`, which the adapter never receives.

The verifier separately changes **only the expected log** at step 2 to `[1, 2, 3, 4]`.
The real library returns `[1, 4]`, and the runner reports the first mismatch.
[View both sides](mismatch.md). This is a mistaken expectation, not an upstream defect.
All five actions may execute before the two-step checked prefix is reported. A diagram
summarizes steps; it does not infer callback timing within a step.

## Optional diagrams for another run

```sh
python interaction_check.py --trace --report dispatch-report.json examples/dispatch/scenario.json -- python examples/dispatch/adapter.py
python examples/dispatch/render.py dispatch-report.json > dispatch-trace.md
```

The renderer lives in this source example, not the installed runner. It requires
complete integer `count`/append-only `log` snapshots and rejects incompatible or partial
states instead of inventing events. Compact reports must be rerun with `--trace`.
Only checked steps are drawn; Markdown tables work without Mermaid.

Run the verifier without `--check` to regenerate [evidence](../../docs/evidence/dispatch.json)
and diagrams. CI reruns the real library and checks source hashes. These are behavior
checks, not coverage or speed measurements.

## A native test may be enough

[native_tests.py](native_tests.py) uses `Dispatcher`, `Property` and `unittest` directly:

```sh
python -m unittest discover -s examples/dispatch -p native_tests.py -v
```

Use that route for one regression. Replay earns its extra files only when sharing
the actions and observations is useful. This example does not cover async delivery,
multiple-listener ordering, rendering, focus, clipboard or application side effects.
[The same explanation for rat-salsa](../../docs/REAL_API_WALKTHROUGH.md).
Loading
Loading