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
33 changes: 30 additions & 3 deletions claude.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,8 @@ flowchart LR
Files[("source files and<br/>staged patch files")]

Engine -->|"3492 moves, deletes (one way),<br/>when a tray is running"| Tray
Engine -->|"3493 inline, settle, and<br/>moves and deletes with no tray"| Owner
Engine -.->|"launch with patch on stdin, or<br/>with a delete, when nothing owns 3493"| Window
Engine -->|"3493 inline, settle, and diff,<br/>moves and deletes with no tray"| Owner
Engine -.->|"launch with patch on stdin, or with<br/>a delete or a pair, when nothing owns 3493"| Window
Tray <-->|"3493 list, accept, focus"| Owner
Window <-->|"3493 listfull, accept, discard"| Owner
Plugin -->|"3493 settle, after accepting"| Owner
Expand Down Expand Up @@ -171,10 +171,29 @@ apart.
`Tray/TrayDetector.cs` as source, because DiffEngine publishes and embeds the heads and a
reference back would be a cycle.
- Holds pending moves and deletes itself when it owns the queue, which is what happens with no
tray installed. They are ordinary `QueueEntryKind.Move`/`Delete` entries — the same ones an
tray installed, and every failing pair DiffEngine resolved it for, whether a tray is running or
not.
- `TrackedWatch` is what keeps those rows honest, and only runs for a queue this process owns. An
owned queue is otherwise push only — a socket message or a launch argument puts an entry in it
and nothing ever revisits it — so rows described the moment they arrived and nothing after.
`OwnerLink.ReadChanges` has always done the equivalent for a displayed queue, on the same 200ms
cadence and the same `FileStamp` test, which is why the two are worth reading together. A pass
that finds nothing must return the identical `SessionState`, or the open context menu closes
five times a second. It stops short of the tray's third rule, dropping a pair whose two files
became byte equal: that check exists because an external diff tool might have converged them,
and here the viewer is the diff tool. They are ordinary `QueueEntryKind.Move`/`Delete` entries — the same ones an
attached viewer draws for the tray's — so nothing about how they look or what their menu offers
is per arrangement. Only who applies them differs: `ViewerActions.MoveFile`/`DeleteFile` here,
a forwarded key there.
- `ViewerMode.File` — two paths on the command line, one window, no port — is reached by nothing in
DiffEngine any more, and is kept deliberately rather than left behind. It is the blocking
one-pair-per-invocation shape a `git difftool` style caller needs, where queue mode's second
invocation forwards and exits and the caller races ahead; it is the only place accepting means
copy rather than move, which is what two arbitrary files a person named deserve; and
`Fixtures.File()` is the "one entry, no queue chrome" state around thirty test call sites are
built on, so collapsing it would re-approve every renderer, scroll and pixel snapshot with a
pending column those tests are not about. It costs a handful of `if`s in `ScreenBuilder`,
`QueueProjection` and `Settle`. Do not delete it because it looks unreachable.
- Single instance by socket bind on 3493 (`DiffEngine_ViewerPort`): whoever binds owns the queue,
and a process that fails to bind talks to the owner instead. A viewer that does not own one runs
with `--attach`: it polls `listfull`, derives every pane from the patches that come back, and
Expand Down Expand Up @@ -242,6 +261,14 @@ apart.
window — the diff tool DiffRunner just launched for that pair — and a delete has no second file
to compare against, so no tool ever opens for it. `--delete <file>` is the launch, on the command
line rather than stdin because a path fits where snapshot content does not.
- Unless that diff tool is the viewer, which is the `Diff` verb and `--diff <received> <target>`.
Then the premise above is false — there is no window for the pair yet — so it is tracked exactly
as a move and a window is raised over the entry, and `DiffRunner` skips the whole process per
pair path: nothing to find already showing it, no window to replace, no `MaxInstance` slot to
spend, and no process for the tray to kill on accept. `DiffRunner.Kill` sends `Settle` for the
move key rather than killing anything, since the row is drawn in a window shared with every other
pending pair. That is what makes ten failing image snapshots one window instead of ten, and it is
only available to the viewer because no other tool can be told to drop one pair.
- The catch that shape creates: every inline transition rebuilds its half of the queue from
`InlineQueue`, so `ViewerSession.Rebuild` carries the tracked entries across it. Without that,
accepting one snapshot silently drops the files pending beside it. `Sync` is the one caller that
Expand Down
6 changes: 6 additions & 0 deletions docs/diff-tool.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ If a tool is running for the comparison of the current verification (per test),
| false | true | Open new instance. Previous instance must be manually closed |
| false | false | Kill current and open new instance |

The bundled [DiffEngineViewer](/docs/viewer.md) is the exception: it queues every failing pair into one window, so none of the four rows describes it.

This allows, in most cases, for no manual closing of the tool to be required.<!-- singleLineInclude: diffToolCleanup. path: /docs/mdsource/diffToolCleanup.include.md -->


Expand Down Expand Up @@ -284,6 +286,10 @@ DiffTools.UseOrder(DiffTool.DiffEngineViewer);

#### Notes:

* The one tool DiffEngine does not open per pair. Every failing pair joins one
window, so the auto-refresh and MDI table above does not describe it: nothing
is relaunched, nothing is killed, and a test that starts passing has its entry
dropped instead
* Bundled inside the DiffEngine package, so it needs no install
* Also available standalone as `DiffEngineViewer.Windows`, `.Mac` or `.Linux`
* Renders natively per platform: WinForms on Windows, AppKit and Core Text on
Expand Down
6 changes: 3 additions & 3 deletions docs/inline.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ flowchart LR
Files[("source files and<br/>staged patch files")]

Engine -->|"3492 moves, deletes (one way),<br/>when a tray is running"| Tray
Engine -->|"3493 inline, settle, and<br/>moves and deletes with no tray"| Owner
Engine -.->|"launch with patch on stdin, or with<br/>a delete, when nothing owns 3493"| Window
Engine -->|"3493 inline, settle, and diff,<br/>moves and deletes with no tray"| Owner
Engine -.->|"launch with patch on stdin, or with<br/>a delete or a pair, when nothing owns 3493"| Window
Tray <-->|"3493 list, accept, focus"| Owner
Window <-->|"3493 listfull (with the owner's moves<br/>and deletes), accept, discard"| Owner
Plugin <-->|"3493 listfull, accept, discard,<br/>focus, via InlineQueueClient"| Owner
Expand All @@ -37,7 +37,7 @@ flowchart LR

The queue of pending snapshots has exactly one owner per session: whichever process bound port 3493 first, decided once and never transferred. When the tray owns it, its edges to the owner above are in-process calls; when a viewer owns it, the tray drives that viewer over the same verbs. Either way both hosts run the same `InlineQueue` implementation, so they cannot disagree on what accepting or settling means. [DiffEngineViewer](/docs/viewer.md) and [DiffEngineTray](/docs/tray.md) cover the two arrangements in detail.

Pending file moves and deletes follow the same rule. They go to the tray when one is running, over the port they have always used, and to the queue owner when one is not — so with no tray installed they are reviewed in the viewer rather than going nowhere. A delete starts a viewer if nothing owns the queue, because it has no second file to compare against and so no diff tool ever opens for it. A move does not: DiffEngine has already opened a diff tool for that file pair.
Pending file moves and deletes follow the same rule. They go to the tray when one is running, over the port they have always used, and to the queue owner when one is not — so with no tray installed they are reviewed in the viewer rather than going nowhere. A delete starts a viewer if nothing owns the queue, because it has no second file to compare against and so no diff tool ever opens for it. A move normally does not, because DiffEngine has already opened a diff tool for that file pair — unless that tool is the viewer itself, in which case there is no separate window to compete with and the pair is queued and raised the way a snapshot is. That is what makes a run failing several file comparisons produce one window rather than one per pair.


## When a test fails
Expand Down
2 changes: 2 additions & 0 deletions docs/mdsource/diff-tool.source.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ If a tool is running for the comparison of the current verification (per test),
| false | true | Open new instance. Previous instance must be manually closed |
| false | false | Kill current and open new instance |

The bundled [DiffEngineViewer](/docs/viewer.md) is the exception: it queues every failing pair into one window, so none of the four rows describes it.

include: diffToolCleanup


Expand Down
6 changes: 3 additions & 3 deletions docs/mdsource/inline.source.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ flowchart LR
Files[("source files and<br/>staged patch files")]

Engine -->|"3492 moves, deletes (one way),<br/>when a tray is running"| Tray
Engine -->|"3493 inline, settle, and<br/>moves and deletes with no tray"| Owner
Engine -.->|"launch with patch on stdin, or with<br/>a delete, when nothing owns 3493"| Window
Engine -->|"3493 inline, settle, and diff,<br/>moves and deletes with no tray"| Owner
Engine -.->|"launch with patch on stdin, or with<br/>a delete or a pair, when nothing owns 3493"| Window
Tray <-->|"3493 list, accept, focus"| Owner
Window <-->|"3493 listfull (with the owner's moves<br/>and deletes), accept, discard"| Owner
Plugin <-->|"3493 listfull, accept, discard,<br/>focus, via InlineQueueClient"| Owner
Expand All @@ -30,7 +30,7 @@ flowchart LR

The queue of pending snapshots has exactly one owner per session: whichever process bound port 3493 first, decided once and never transferred. When the tray owns it, its edges to the owner above are in-process calls; when a viewer owns it, the tray drives that viewer over the same verbs. Either way both hosts run the same `InlineQueue` implementation, so they cannot disagree on what accepting or settling means. [DiffEngineViewer](/docs/viewer.md) and [DiffEngineTray](/docs/tray.md) cover the two arrangements in detail.

Pending file moves and deletes follow the same rule. They go to the tray when one is running, over the port they have always used, and to the queue owner when one is not — so with no tray installed they are reviewed in the viewer rather than going nowhere. A delete starts a viewer if nothing owns the queue, because it has no second file to compare against and so no diff tool ever opens for it. A move does not: DiffEngine has already opened a diff tool for that file pair.
Pending file moves and deletes follow the same rule. They go to the tray when one is running, over the port they have always used, and to the queue owner when one is not — so with no tray installed they are reviewed in the viewer rather than going nowhere. A delete starts a viewer if nothing owns the queue, because it has no second file to compare against and so no diff tool ever opens for it. A move normally does not, because DiffEngine has already opened a diff tool for that file pair — unless that tool is the viewer itself, in which case there is no separate window to compete with and the pair is queued and raised the way a snapshot is. That is what makes a run failing several file comparisons produce one window rather than one per pair.


## When a test fails
Expand Down
12 changes: 11 additions & 1 deletion docs/mdsource/viewer.source.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,18 @@ One package per operating system rather than one for all of them, because WinFor

## Usage

Comparing two files:
Comparing two files, in a window of its own:

```
DiffEngineViewer <left> <right>
```

Comparing a failing pair, which DiffEngine sends when the diff tool it resolved for that pair is the viewer. Queued rather than given its own window, so later pairs join it:

```
DiffEngineViewer --diff <received> <target>
```

Reviewing an inline snapshot, where the patch payload arrives on stdin:

```
Expand Down Expand Up @@ -77,6 +83,10 @@ Nothing is written to disk for inline review. The patch travels over stdin, or o

A test run that fails several inline snapshots produces one window, not several. Whichever process binds the loopback port holds the queue; everything else hands its patch to that one. The window lists everything pending and offers **Accept all**.

Failing file comparisons join the same queue, so a run that fails ten snapshots opens one window whether they are inline or on disk. Every other diff tool gets a process per pair, and DiffEngine closes each one as its test starts passing; the viewer is told to drop that row instead.

Rows that came from files follow those files. A re-run that rewrites a received file shows the rewrite, a verified file that appears fills in the other pane, and a row whose received file goes away leaves with it — so nothing is offered for a file that is no longer there, however it went. The window closes once the last row does.

The list sits in a column on the left. Drag the divider beside it to widen the column when the file names are longer than it is. When the list outgrows the window it follows the selection, keeping the selected row visible.

Row labels are the shortest thing that tells one entry from another, so hovering one fills in what it left out: the whole path, the test behind a call site, every framework behind a conflict, and the failure behind a `!`. A row with nothing to add shows no tooltip at all.
Expand Down
12 changes: 11 additions & 1 deletion docs/viewer.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,12 +39,18 @@ One package per operating system rather than one for all of them, because WinFor

## Usage

Comparing two files:
Comparing two files, in a window of its own:

```
DiffEngineViewer <left> <right>
```

Comparing a failing pair, which DiffEngine sends when the diff tool it resolved for that pair is the viewer. Queued rather than given its own window, so later pairs join it:

```
DiffEngineViewer --diff <received> <target>
```

Reviewing an inline snapshot, where the patch payload arrives on stdin:

```
Expand Down Expand Up @@ -84,6 +90,10 @@ Nothing is written to disk for inline review. The patch travels over stdin, or o

A test run that fails several inline snapshots produces one window, not several. Whichever process binds the loopback port holds the queue; everything else hands its patch to that one. The window lists everything pending and offers **Accept all**.

Failing file comparisons join the same queue, so a run that fails ten snapshots opens one window whether they are inline or on disk. Every other diff tool gets a process per pair, and DiffEngine closes each one as its test starts passing; the viewer is told to drop that row instead.

Rows that came from files follow those files. A re-run that rewrites a received file shows the rewrite, a verified file that appears fills in the other pane, and a row whose received file goes away leaves with it — so nothing is offered for a file that is no longer there, however it went. The window closes once the last row does.

The list sits in a column on the left. Drag the divider beside it to widen the column when the file names are longer than it is. When the list outgrows the window it follows the selection, keeping the selected row visible.

Row labels are the shortest thing that tells one entry from another, so hovering one fills in what it left out: the whole path, the test behind a call site, every framework behind a conflict, and the failure behind a `!`. A row with nothing to add shows no tooltip at all.
Expand Down
Loading
Loading