Skip to content

Tell the body about a tall footer and the loop about an unseen window, and carry held deletes to the viewer - #930

Merged
SimonCropp merged 8 commits into
mainfrom
native-rows-and-held-deletes
Oct 4, 2026
Merged

SimonCropp merged 8 commits into
mainfrom
native-rows-and-held-deletes

Conversation

@SimonCropp

Copy link
Copy Markdown
Member

Two groups from todo.md: three changes to the native heads that share one ABI bump, and held deletes carried beyond the tray's own menu.

Needs attention before merging

  • The Swift is unverified. It has never been compiled; this PR's macos-14 job is its first build. That job only captures, so a green build exercises none of the three changes. The checks for a person on a Mac are in todo.md.
  • Native binaries. DEVIEW_VERSION goes from 11 to 12 and the committed binaries are 11, so the native jobs and the unix pixel job report a mismatch until the build-native PR is merged into this branch.
  • Documents on a covered Mac window. DocumentWatch does nothing while a head reports its window unseen, as for a hidden one. On macOS that now includes a wholly covered window: if occlusionState is ever wrong, pages stall.
  • A move arriving in an owning viewer removes the delete pending on its target, which can be the entry on screen, and closes an open menu as any removal does.
  • A held delete's row has the same ! mark as a failed entry. Only the tooltip tells them apart.
  • The listing gains a line. held: key|reason follows a delete. An older reader skips it and an older owner sends none.

The ABI round (DEVIEW_VERSION 12)

  • Taller footer. DeviewInput.rows is capped by the rows the body has room for plus the model's eight chrome lines, so a footer taller than its allowance takes rows from the body and no longer covers the last ones. MeasureGrid on Linux, Renderer.grid(for:) on macOS. No managed change beyond the version.
  • Unseen window. DeviewInput.unseen is a head saying nobody can see its window: hidden or minimised on Linux, ordered out, miniaturised or occluded on macOS, minimised on Windows. ViewerProgram.Loop slows OwnerLink, TrackedWatch and DocumentWatch on it as it does for its own hide. A minimised Linux window is no longer built or drawn.
  • Drag. A drag moves the centre the frame asked for and clamps it to the wider of the two panes' ranges, where it clamped to the dragged pane's own and pulled the other picture into it. The same arithmetic in the shim, the Swift and WinForms. WinForms never had the native heads' earlier fix, so on an axis its picture could not move on it pulled the other pane to the middle; that is fixed too.

Linux was built and run in an ubuntu:24.04 container: the shim builds with no warnings and PixelTests is 23 of 23. All 22 existing baselines reproduce byte for byte and one scene is added, ATallFooterTakesRowsFromTheBody. That a minimised window is not drawn (0 of 60 changed screens, from 60 of 60) and the drag were checked by hand there under openbox with xdotool, which CI does not have.

Left, each with its reason in todo.md: a Windows capture with a tall footer, making the macOS pump interruptible, and a covered window on Linux and Windows.

Held deletes

  • An owning viewer's batch had the bug. With a delete queued after the move onto its file, accept-all left no file at the target. It now goes by the tray's rules: a move arriving withdraws the delete, a move carried out marks it, a bulk accept leaves a marked or awaited delete, and accepting it on its own still deletes.
  • The hold is on a full listing, from either owner. An attached viewer shows it as the entry's status, and the wire's accept-all answer says why deletes were kept.
  • "Accept all in" a group from an attached viewer leaves a held delete, by not sending its key. Chosen over a new wire verb: an owner too old to report holds would not know a new verb either, and is sent every key as before.

Tests

dotnet build src --configuration Release is clean and dotnet test --solution src/DiffEngine.slnx --configuration Release passes on Windows: 3,356 tests, 0 failed, 35 skipped, and again with the process held to two cores.

todo.md and claude.md are updated for both groups.

SimonCropp and others added 8 commits October 4, 2026 16:39
An owning viewer's accept-all took its moves and deletes in queue order,
and EnqueueTracked replaced by key only, so a delete raised after a move
onto the same verified file followed that move in the batch: the received
file was moved into place and then deleted, and neither was left. The
tray's tracker had the same shape and these are its rules.

A move that arrives withdraws the delete pending on its target. A move
that is carried out, on its own or in a batch, marks the delete pending on
the file it wrote (QueueEntry.Written), and the entry says why. A bulk
accept leaves a delete that is marked, or whose file a move still pending
is going to write, and says so after its counts. Accepting the delete on
its own still carries it out, and a run raising it again lets go of the
hold. The watch over owned files reads the delete's file again once the
move has written it, so the entry built from that read keeps the mark.

MoveOntoDeleteTests holds each of those. Run before the fix, the accept-all
and the group accept left no file at the target, a move did not withdraw
the delete, and a second accept-all deleted what the first had accepted.
…ll answer

A delete held because a move wrote its file, or is still to, was marked in
the tray's menu and debug view only. A viewer showing the tray's queue
showed it like any other, and the answer to its accept-all counted it as
kept without saying why.

A full listing now carries the reason on a `held` line beside the delete's
own (ViewerResponseDelete.Held). A line of its own, since `delete` is read
by its field count: a reader that predates it skips the line, and an owner
that predates it sends none, which reads as nothing held. Both owners say
it, the tray from Tracker.HeldReason and an owning viewer from
ViewerSession.HeldReason. An attached viewer puts it on the entry as its
status, so the row carries the mark a failure does and its tip says why.
The tray's answer to a wire accept-all ends with the sentence its own
balloon uses when a delete of the batch was kept for a move.

TrackedDelete.Written is the one thing a listing carries that changes on a
tracked object that stays the same object, so its two changes are counted
into ITrackedFiles.Version with the restores. Without that the tray
answered "unchanged" to a viewer still showing a hold a re-run had let go
of, which AnAttachedViewerSaysWhyTheTrayHoldsADelete failed on with the
count taken out.

Checked by round trips in ViewerProtocolTests, including a listing with no
hold line, one whose hold precedes its delete and one whose hold names no
delete; by AttachedViewerTests for the entry, which stays the same entry
from pump to pump; and by TrayViewerSyncTest over a socket for both
arrangements.
"Accept all in" a group from a viewer showing someone else's queue sends
one Accept per key, and an owner carries out an accept by key as asked,
held or not, since that is how a reviewer says a file is redundant. So a
group over a move and the delete pending on its target moved the received
file into place and then deleted it, which the owner's own accept-all does
not do.

The listing now says which deletes the owner holds, so the group accept
does not send those keys and says so in its message. Chosen over a wire
verb for a group accept because the owner already tells the viewer all it
needs, the rule about deletes waiting on the group's snapshots already
lives on this side, and an owner old enough not to report holds would not
know a new verb either: it reports none and is sent every key, as before.
The holds are read from the latest listing when the deletes' turn comes. A
delete waiting on one of the group's own moves is held before that move is
accepted and after it, so a listing from either side of the accept says so.

Checked over a socket for both arrangements in TrayViewerSyncTest, and in
AttachedViewerTests against an owner that says a delete is held and one
that does not. With the skip taken out the file the move wrote was gone
after the group accept, from a tray and from an owning viewer.
The managed side slices the body for the rows a head says its window has, less
eight lines for everything that is not a row. Both native heads reported the
window's height in rows whatever their footer came to. Eight lines are more
than a title, the headers and one row of buttons take, so a footer of two or
three rows fitted in what was over. A taller one did not: a paged document's
buttons are four rows in a Linux window under about 450 pixels wide, and the
last rows the managed side sliced were laid out under them. On macOS, past
three rows of buttons, or two and a status line, the last one or two were not
drawn.

DeviewInput.rows is now no more than the body has room for with those eight
lines added back, counted from the last frame laid out for the window: by
MeasureGrid from where the table put its first row and where the body ends,
and by Renderer.grid(for:) from the capacity the last window draw worked out,
when that was at the size asked about. Where the footer fits, which is every
window there was a baseline or a habit for, the answer is the one it always
was. No struct moved, but what a field means did, so DEVIEW_VERSION is 12:
the committed binaries are refused until build-native has rebuilt them.

No managed change beyond the version. The WinForms head already reports the
rows its canvas can draw. Its capture, which builds a screen before its footer
is laid out, is as it was.

Checked in an ubuntu:24.04 container set up as the unix job is. The 22 scenes
and window tests PixelTests had reproduce byte for byte against the new shim.
ATallFooterTakesRowsFromTheBody shows the window, asks it for its rows with a
footer of one row and of five, and takes a picture of the second screen built
for what was reported: 41 rows and 39, the last line sliced the one above the
buttons. Against a shim with the limit taken out it fails, told 41 for both.
The shim builds with no warnings. The Swift is unverified: it cannot be built
from Windows, and CI's macOS job only captures, which never measures a window.
…inux window alone

The loop keeps three things going beside a window for whoever is reading it:
OwnerLink lists the queue's owner, TrackedWatch looks at the pending files
again, and DocumentWatch reads and draws the documents on screen. Each slows or
stops for a hidden window, and the loop only knew a window was hidden when it
had hidden it itself. A window in the taskbar or the Dock, or wholly behind
another on macOS, was listed, watched and drawn for as one on screen. The
WinForms and macOS heads already slowed their own frame wait for those, which
is as far as a head could go.

A head now says so with every poll: DeviewInput.unseen, and ViewerInput.Unseen
from it. The loop slows the three watchers while its own hide or the head's
word says nobody is looking, the two kept apart since they come and go apart.
It is a state, as the grid is, so it makes no frame one with input in it.

- Linux: hidden or minimised. A window behind another is not known to be, since
  X11 says nothing of it that GLFW passes on. A minimised window is also no
  longer built and drawn when its screen changes, as a hidden one is not:
  coming back from the taskbar is an arrival that marks it stale, as being
  shown is.
- macOS: what its pump already asks before waiting longer, which is ordered
  out, miniaturised, or with nothing of it showing.
- Windows: minimised. A window behind another is not asked about.

The field goes at the end of DeviewInput, in the bump to 12 the commit before
made.

Left: the macOS pump still cannot be interrupted, so a window nobody can see
comes forward up to a tenth of a second late for a patch arriving over the
socket.

Checked by two tests of ViewerProgram.Loop over a window that says, poll by
poll, whether it can be seen, the first of which fails with the loop's answer
taken out, and by a WinForms test that minimises a form on a desktop of its
own, which fails against a head that says nothing. In the container, a real
window under Xvfb and openbox, minimised with xdotool and brought back with
wmctrl, presented sixty changed screens each time and counted by a GL query:
before this it drew 60 of 60 minimised and now draws none, says unseen while
minimised and while hidden and not otherwise, and draws again once restored.
That check is not in the suite, since CI has no window manager. PixelTests
pass against the new shim, every baseline as it was. The Swift is one line and
unverified.
An enlarged picture's centre is one point for both panes, and each pane moves
it in as far as its own picture needs to keep its space full. A drag started
from the centre the dragged pane had drawn about and was kept inside that
pane's range. With two pictures of different shapes the ranges differ, so the
first move of a drag in the pane that can go less far brought the other pane's
picture in from wherever beyond that range it had been taken, on an axis both
can move on.

A drag now moves the centre the frame asked for, and keeps it inside what the
pane showing less of its picture can show, which is the wider of the two
ranges since both are about the middle. The same arithmetic in all three
heads: UpdatePan in the shim, PictureSpace.dragged on macOS and
PicturePlacement.Dragged in the WinForms head, each handed how the other
pane's picture was placed when the button went down. A drag in the pane that
can go less far, from a centre beyond its own range, moves the other pane's
picture alone until the centre is back inside it.

The WinForms head needed more than the native ones: it had not been given
their earlier fix either, so on an axis its picture could not move on at all
it reported the middle, and a sideways drag took the other pane back to its
middle row. An axis the dragged picture cannot move on is now left as the
model had it there too.

No struct changes: what DeviewInput.panX and panY are kept inside does, which
the header says under the bump to 12 this round already made.

Checked on Windows by three tests of PicturePlacement and one through the
canvas with a wide picture beside a tall one, all four of which fail against
the old arithmetic. In the container, a real window under Xvfb with the same
pair, the centre asked for as far down as there is, and the left picture
dragged sideways by xdotool: the shim before this reported 0.7099 down, the
left pane's own limit, and this one 0.7824, the right pane's, where it already
was. Dragged to a corner from either pane both now stop at 0.2066 across and
0.7824 down, where the right pane stopped at 0.276 across and the left at
0.7099 down. From the middle every drag reports what it did. That check is
not in the suite, since nothing there can move a pointer. PixelTests pass
against the new shim with every baseline as it was. The Swift is unverified.
The held delete items and the three that needed the ABI to move go, replaced
by what each fix left: a hold let go by a re-raise, a listing taken while a
move is in flight, a covered window that only macOS knows of, and what a
person on a Mac has to confirm. claude.md gains ABI 12, the drag's
arithmetic, and the hold on the wire and in an owning viewer.
Co-authored-by: SimonCropp <122666+SimonCropp@users.noreply.github.com>
@SimonCropp
SimonCropp merged commit 0cc45b9 into main Oct 4, 2026
13 checks passed
@SimonCropp
SimonCropp deleted the native-rows-and-held-deletes branch October 4, 2026 07:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant