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
35 changes: 27 additions & 8 deletions claude.md
Original file line number Diff line number Diff line change
Expand Up @@ -163,8 +163,12 @@ write. Every patch is told what `Apply` would have told it in turn, and `Apply`
case of the same code. One thing can only differ: a write that fails fails every patch that
edited, and any other that would have had to edit the file as it was read, while the rest keep
what is true of the file. Both batches use it, a file at a time (`AcceptBatch.Together` in the viewer,
`OwnedInlineHost.AcceptEvery` in the tray), so the moment up to which a snapshot can still be
withdrawn from a bulk accept is its file's turn rather than its own. A `SourceScan` rents its map
`OwnedInlineHost.AcceptEvery` in the tray). Each patch that edits is asked about once the file is
patched in memory and before its one write, with the file's lock held: one no longer wanted,
discarded or settled while the file was waited for, is `InlineApplyStatus.Withdrawn`, the file is
patched again from what was read without it, and a batch counts it as nothing. The viewer answers
from `host.State` without the session's lock, because a wire accept applies inside that lock and
would wait on the file while the question waited on it; the tray answers under its gate. A `SourceScan` rents its map
from the pool and is disposed for that reason, and keeps its spans as sorted lists rather than
hash tables. A batch lexes a file once and carries the scan from one editing patch to the next
(`SourceScan.Edited`): lexing starts again at the edit's line, never at a line that follows a
Expand Down Expand Up @@ -228,7 +232,10 @@ the comment there about not caching "nothing staged" asks for.
`Application.DoEvents` rather than `Application.Run`, so the shared loop stays shared. Only the
grid is owner drawn: the footer, the context menu, the pane scrollbar and the tooltips are real
controls, so they get the OS's keyboard handling, theming and screen reader support. The menu is
still projected from the same `Screen.Menu` the other heads draw. A row is handed to GDI+ cut to
still projected from the same `Screen.Menu` the other heads draw. A pane's header too long for
its pane is cut to whole cells with an ellipsis in the last, the left one a gap short of the
right pane (`ViewerCanvas.HeaderShown`), as the Linux head's table cuts its own. A capture's
caller asks `FormsViewerWindow.MeasureGrid` for the grid a screen's footer leaves. A row is handed to GDI+ cut to
the cells its pane has, and one more (`RowText.Shown`, read from the front of the row and cut
before it is segmented): GDI+ lays out every character it is given before it clips any, so 72
rows of 2,000 character lines were 15 ms a paint, and a megabyte line 24. A picture zoomed to
Expand Down Expand Up @@ -353,8 +360,10 @@ the comment there about not caching "nothing staged" asks for.
would not take staying in the queue with what the applier said, and it counts as still needing
review only its own members. The bulk discards are the same batch with `Discarding` set:
snapshots and pending deletes go as it begins, since nothing of theirs is on disk, and each
move's received file is thrown away outside the lock. A discard under way is not on an owner's
listings. No inline transition rebuilds the whole list any more. An arrival, a settle, a
move's received file is thrown away outside the lock. A discard under way is on an owner's
listings as the same counts and a `discarding` line, which an older reader skips and so takes
for an accept; an attached window says "Discarding n of m" and refuses what changes the queue.
No inline transition rebuilds the whole list any more. An arrival, a settle, a
discard and a single accept read what changed off the `InlineQueue` before and after, whose
untouched items come back as the same instances, and edit the list where it stands
(`TryRebuildChanged`), with `RebuildWhole` as the fallback and as what the tests hold it to.
Expand Down Expand Up @@ -664,7 +673,11 @@ the comment there about not caching "nothing staged" asks for.
anything held the queue is `Failed`, so the caller stages; it used to be waited on for the whole
of `BindWait` and reported as launched, and an inline snapshot was then in no queue and not
staged either. A clean exit is left to the wait, since a viewer that hands its work to an owner
exits with zero. `ViewerContract` is the other half: resolution passes over a copy older than
exits with zero. A viewer that could not show its patch and staged everything it held exits 5
(`ViewerExit.Staged`): the gate reports `Staged`, `AddInlineAsync` answers
`InlineResult.Staged`, and the caller stages nothing, where both used to stage a trio. It is a
failure to any older library and never returned by an older viewer, so `ViewerContract` asks
nothing for it, and a viewer started for a delete or a pair never says it. `ViewerContract` is the other half: resolution passes over a copy older than
20.5.0, which exits on `--payload`, when a newer one is further down the search order.
- 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
Expand Down Expand Up @@ -715,8 +728,14 @@ the comment there about not caching "nothing staged" asks for.
passes that path as it was given. A tool started through a script, or one that hands over to
another process and exits, cannot be tracked from here at all.
- A move that writes a file marks the delete pending on it (`TrackedDelete.Written`), however
the move was accepted, and no accept-all carries a marked delete out until a run raises it
again; accepting it on its own still does. `Tracker.HeldReason` is what the menu and the debug
the move was accepted, and no accept-all carries a marked delete out until it is raised again
over a file written since (`TrackedDelete.WrittenAs`, `QueueEntry.WrittenAs`): raised over the
file as the move left it, it stays held, since a process that decided before the move sends
the same message. Accepting it on its own still does. A move counts as still to write its file
from before it leaves `moves` until it lands (`Tracker.accepting`), so a listing taken mid
accept holds its delete. A held delete's row leads with `~` in its label and is handed to the
heads with no status, so none draws it as a failure (`QueueProjection.HeldMark`,
`QueueEntry.Held`), and the tray's menu marks it `~` where a failure is `!`. `Tracker.HeldReason` is what the menu and the debug
view show, and it rides a full listing as a `held: key|reason` line of its own
(`ViewerResponseDelete.Held`), since the `delete` line is parsed by field count and an older
reader skips a line it does not know. An attached viewer shows it as the entry's status and
Expand Down
2 changes: 1 addition & 1 deletion docs/inline.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ DiffEngineViewer --inline --source <source file> --line <number> < the.inlinepat

For the producing side — a test library with a failing inline snapshot:

* `DiffRunner.AddInlineAsync(patch)` queues a patch with whatever owns the port, launching the bundled viewer when nothing does. Returns `Queued`, `Disabled` (build servers, continuous testing and AI CLIs included), or `NoViewerFound` — the caller's cue to stage files and fall back to a text diff. A viewer that was started and exited with a failure before it held the queue is `NoViewerFound` as well: one too old for the launch, or with no runtime to run on, took nothing.
* `DiffRunner.AddInlineAsync(patch)` queues a patch with whatever owns the port, launching the bundled viewer when nothing does. Returns `Queued`, `Disabled` (build servers, continuous testing and AI CLIs included), or `NoViewerFound` — the caller's cue to stage files and fall back to a text diff. A viewer that was started and exited with a failure before it held the queue is `NoViewerFound` as well: one too old for the launch, or with no runtime to run on, took nothing. `Staged` is a viewer that was started, could not show the snapshot, and staged it itself under the source project's `obj/VerifyInline`: the caller should not stage it a second time.
* `DiffRunner.SettleInline(sourceFile, line)` drops the pending entry for a call site, for when a previously failing test passes. Unknown entries and an absent owner are no-ops, so call it freely. The settle carries the running framework, so a multi-targeted run only settles its own variant of a conflicted entry. That framework is the running process's, which makes this the test run's verb and only the test run's: a surface applying a patch of its own wants `SettleAppliedInline`, [below](#applying-a-patch-from-another-surface). Pass `memberName` and `value` too: `value` is what the passing call's expected argument holds, as the library compared it (for F#, after `SourceLanguage.SnapshotValue`). Once an accept above a call site moves it, its line no longer names its entry and the member is the fallback. `value` narrows that fallback to an entry the value settles, one anchored to it or waiting to become it. Without `value`, a passing call can settle the entry of a failing sibling in the same member. The line can also come to name another call's entry, so one found under it that was queued from a different member is left alone unless `value` settles it. A failing re-run of a call site that has moved is recognised the same way, by its member, test and anchor, and updates its entry rather than queueing a second one beside it.
* `InlineStaging.Settle(sourceFile, line, memberName)` clears what the running framework staged for a call site that now passes. `InlinePatchFile.Write` labels a patch that carries no framework with the running one, so a framework that passes does not clear what another one staged. `InlineStaging.Clear` still clears every framework's, which is what retiring a call site wants.
* An absent owner is also remembered. A port found with nothing listening is taken as still unowned for ten minutes, and the sends that only tell the owner something — settle, retire, a move or delete to track, the first attempt to queue a patch — return without connecting while that stands. A refused loopback connection is not free on Windows: the firewall's stealth mode, on by default, drops the reset a closed port would answer with, so each refusal takes two seconds, and a green run settling once per inline verification was spending minutes on them. Anything that has to reach an owner probes for itself before launching a viewer, and that probe, like every listing, always connects and corrects the memory with what it finds.
Expand Down
2 changes: 1 addition & 1 deletion docs/mdsource/inline.source.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ DiffEngineViewer --inline --source <source file> --line <number> < the.inlinepat

For the producing side — a test library with a failing inline snapshot:

* `DiffRunner.AddInlineAsync(patch)` queues a patch with whatever owns the port, launching the bundled viewer when nothing does. Returns `Queued`, `Disabled` (build servers, continuous testing and AI CLIs included), or `NoViewerFound` — the caller's cue to stage files and fall back to a text diff. A viewer that was started and exited with a failure before it held the queue is `NoViewerFound` as well: one too old for the launch, or with no runtime to run on, took nothing.
* `DiffRunner.AddInlineAsync(patch)` queues a patch with whatever owns the port, launching the bundled viewer when nothing does. Returns `Queued`, `Disabled` (build servers, continuous testing and AI CLIs included), or `NoViewerFound` — the caller's cue to stage files and fall back to a text diff. A viewer that was started and exited with a failure before it held the queue is `NoViewerFound` as well: one too old for the launch, or with no runtime to run on, took nothing. `Staged` is a viewer that was started, could not show the snapshot, and staged it itself under the source project's `obj/VerifyInline`: the caller should not stage it a second time.
* `DiffRunner.SettleInline(sourceFile, line)` drops the pending entry for a call site, for when a previously failing test passes. Unknown entries and an absent owner are no-ops, so call it freely. The settle carries the running framework, so a multi-targeted run only settles its own variant of a conflicted entry. That framework is the running process's, which makes this the test run's verb and only the test run's: a surface applying a patch of its own wants `SettleAppliedInline`, [below](#applying-a-patch-from-another-surface). Pass `memberName` and `value` too: `value` is what the passing call's expected argument holds, as the library compared it (for F#, after `SourceLanguage.SnapshotValue`). Once an accept above a call site moves it, its line no longer names its entry and the member is the fallback. `value` narrows that fallback to an entry the value settles, one anchored to it or waiting to become it. Without `value`, a passing call can settle the entry of a failing sibling in the same member. The line can also come to name another call's entry, so one found under it that was queued from a different member is left alone unless `value` settles it. A failing re-run of a call site that has moved is recognised the same way, by its member, test and anchor, and updates its entry rather than queueing a second one beside it.
* `InlineStaging.Settle(sourceFile, line, memberName)` clears what the running framework staged for a call site that now passes. `InlinePatchFile.Write` labels a patch that carries no framework with the running one, so a framework that passes does not clear what another one staged. `InlineStaging.Clear` still clears every framework's, which is what retiring a call site wants.
* An absent owner is also remembered. A port found with nothing listening is taken as still unowned for ten minutes, and the sends that only tell the owner something — settle, retire, a move or delete to track, the first attempt to queue a patch — return without connecting while that stands. A refused loopback connection is not free on Windows: the firewall's stealth mode, on by default, drops the reset a closed port would answer with, so each refusal takes two seconds, and a green run settling once per inline verification was spending minutes on them. Anything that has to reach an owner probes for itself before launching a viewer, and that probe, like every listing, always connects and corrects the memory with what it finds.
Expand Down
2 changes: 1 addition & 1 deletion docs/mdsource/tray.source.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ Exiting the tray writes any still-pending inline snapshots back to disk, under t

"Accept all" will accept all pending moves, deletes and inline snapshots. Snapshots whose target frameworks disagree about the content are skipped rather than picked between; resolve those in the viewer.

The deletes it carries out are the ones that were pending when it began. A delete can be the last copy of a snapshot that is moving inline, so the deletes are held back, and the tray says so, when a snapshot could not be written or when a viewer that owns the queue did not answer. A delete of a file that an accepted move has written is left pending rather than carried out, and stays that way through later "Accept all"s: it is marked `!` in the menu, with the reason. It goes when it is accepted on its own, or when a test run raises the delete again.
The deletes it carries out are the ones that were pending when it began. A delete can be the last copy of a snapshot that is moving inline, so the deletes are held back, and the tray says so, when a snapshot could not be written or when a viewer that owns the queue did not answer. A delete of a file that an accepted move has written is left pending rather than carried out, and stays that way through later "Accept all"s: it is marked `~` in the menu, with the reason. It goes when it is accepted on its own, or discarded and raised by a later run. A test run raising it again does not release it unless the file has changed since the move wrote it.

A long queue takes a while to accept. An open [DiffEngineViewer](/docs/viewer.md) window shows how far it has got, with each snapshot leaving the list as it lands.

Expand Down
2 changes: 1 addition & 1 deletion docs/mdsource/viewer.source.md
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,7 @@ Rows that came from files follow those files. A re-run that rewrites a received

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.
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, the failure behind a `!`, and why a delete marked `~` is held. A row with nothing to add shows no tooltip at all.

The panes carry a scrollbar, which moves with the keys and the wheel.

Expand Down
2 changes: 1 addition & 1 deletion docs/tray.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ Exiting the tray writes any still-pending inline snapshots back to disk, under t

"Accept all" will accept all pending moves, deletes and inline snapshots. Snapshots whose target frameworks disagree about the content are skipped rather than picked between; resolve those in the viewer.

The deletes it carries out are the ones that were pending when it began. A delete can be the last copy of a snapshot that is moving inline, so the deletes are held back, and the tray says so, when a snapshot could not be written or when a viewer that owns the queue did not answer. A delete of a file that an accepted move has written is left pending rather than carried out, and stays that way through later "Accept all"s: it is marked `!` in the menu, with the reason. It goes when it is accepted on its own, or when a test run raises the delete again.
The deletes it carries out are the ones that were pending when it began. A delete can be the last copy of a snapshot that is moving inline, so the deletes are held back, and the tray says so, when a snapshot could not be written or when a viewer that owns the queue did not answer. A delete of a file that an accepted move has written is left pending rather than carried out, and stays that way through later "Accept all"s: it is marked `~` in the menu, with the reason. It goes when it is accepted on its own, or discarded and raised by a later run. A test run raising it again does not release it unless the file has changed since the move wrote it.

A long queue takes a while to accept. An open [DiffEngineViewer](/docs/viewer.md) window shows how far it has got, with each snapshot leaving the list as it lands.

Expand Down
2 changes: 1 addition & 1 deletion docs/viewer.md
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,7 @@ Rows that came from files follow those files. A re-run that rewrites a received

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.
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, the failure behind a `!`, and why a delete marked `~` is held. A row with nothing to add shows no tooltip at all.

The panes carry a scrollbar, which moves with the keys and the wheel.

Expand Down
Loading
Loading