Take the group headings out of the host grid, and drop onto a group card

A first group turned the wrap of host cards into an accordion: SidebarRows
interleaves a full-width fold-away heading — chevron, name, count — between the
cards, and in a grid that reads as a dropdown somebody left open. The desktop
grid binds VisibleHosts now. The headings and the fold stay for the phone, whose
list has no room for the row of group cards the desktop draws above the grid.

What a heading said, the card says: HostRowViewModel.GroupLabel, resolved once
per reload like the tag names, drawn as an accent chip and absent from a host in
no group — or in one that has been deleted, which is the same thing to look at.

What a heading also was is the drop target, and that moves to the group cards.
Two things go with it. A host dropped onto another host card used to be filed
beside it, which was legible while a heading named the band of cards it landed
in and is guesswork now; it is refused. And UNGROUPED was how a host was dragged
back out of a group; the way out is the picker in its own editor, which is the
one place "no group" can be said in words.

A drag held at either edge of the grid scrolls it. Without that the gesture only
works for whoever can see both ends of it: the group cards are the first thing in
the scroller, the host may be the fortieth card down, and a drag cannot use the
wheel. A step per drag event rather than a timer, so it follows the pointer and
stops when it stops.

The two heading-shaped tests are replaced. TheHostsGridHoldsCardsAndNoGroupHeadings
asserts the grid's contents rather than only measuring them, because a heading
that came back would lay out perfectly cleanly. TheGroupCardsAreWhatAcceptsADroppedHost
raises a real DragOver over both kinds of card and checks the effects and the
mark — the nearest a headless test gets to a gesture no headless test can
synthesise. manual-checks 3.1-3.2 and 7.6-7.9 follow.
This commit is contained in:
2026-08-03 15:49:58 +02:00
parent 1b7df47537
commit f9d08b738c
8 changed files with 432 additions and 147 deletions
+42 -31
View File
@@ -269,33 +269,32 @@ single-process test can reach.
Open the hosts screen without creating any group.
**Pass:** the grid is the flat wrap of host cards it always was — no GROUPS section above it, no headings
between the cards, no UNGROUPED, nothing saying the hosts are unfiled.
**Pass:** the grid is the flat wrap of host cards it always was — no GROUPS section above it, no chip on any
card, nothing saying the hosts are unfiled.
**Failure means:** the "invisible until used" property is gone, and every existing user gets a heading they
did not ask for. `RebuildSidebarRows` returns early when `Groups` is empty; that early return is the feature.
**Failure means:** the "invisible until used" property is gone, and every existing user gets a section they
did not ask for. `HasGroups` is what hides the row of group cards; `HostRowViewModel.HasGroup` hides the chip.
### 3.2 Filing hosts, and folding a heading
### 3.2 Filing hosts, and reading which group a card is in
Make two groups, file some hosts into each through the host editor, then click a heading.
Make two groups and file some hosts into each through the host editor.
**Pass:** the heading's chevron flips and its hosts disappear; the count on the heading does not change,
because it counts what is in the group rather than what is on screen. Clicking again brings them back.
**Also check:** clicking a heading does not change which host is selected — the drawer stays open on the
same machine and its EDIT and DELETE go on acting on it. This is asserted in a test, but the test drives the
view model directly; what it cannot see is whether the `ListBox` writes something else back through the
binding first.
**Pass:** every filed card carries the group's name as an accent chip under its address; the cards stay one
flat wrap, in one order, with nothing between them. **There is no heading and no fold on the desktop** — the
headings, their chevrons and UNGROUPED are the phone's, whose list has no room for a row of group cards.
**Then press a group card.** The grid narrows to that group's hosts, the card is marked as chosen, and
SHOW ALL appears beside GROUPS. Pressing it brings the rest back and unmarks the card.
**Failure means:** a full-width bar with a chevron between the cards is the old grouping coming back through
`SidebarRows` — the desktop grid binds `VisibleHosts`. See `HostsScreen.axaml`.
### 3.3 Deleting a group with hosts in it
Select a group with hosts and press DELETE.
**Pass:** the question names how many hosts are filed under it and says they stay. Agreeing removes the
group; the hosts reappear under UNGROUPED with everything else about them unchanged.
group; the hosts lose their chip and are otherwise unchanged.
**Failure means:** if the hosts vanish, the delete is rewriting host payloads, which it must not — see
`HostGroupRepository`.
@@ -305,8 +304,8 @@ group; the hosts reappear under UNGROUPED with everything else about them unchan
Make a group on machine A, file a host into it, sync. On machine B, sync, then delete the group and sync
again. Back on A, sync.
**Pass:** A shows the host under UNGROUPED. Open that host's editor: the group picker shows "(a group that is
no longer here)" and *keeps it selected*. Change the port and save.
**Pass:** A shows the host with no chip on its card, as though it were in no group. Open that host's editor:
the group picker shows "(a group that is no longer here)" and *keeps it selected*. Change the port and save.
**Failure means:** if the picker opened on "No group", saving has just unfiled the host — quietly, as a side
effect of an unrelated edit. That is the case `BuildGroupChoices` adds the placeholder for.
@@ -642,33 +641,45 @@ Start 7.1's slow connection and press GIVE UP (or the tab's cross) while it is s
opened — it is a real shell, and one running with nothing naming it would be worse than one that comes
back.
### 7.6 Dragging a host into a group · **least covered, like all drag and drop**
### 7.6 Dragging a host onto a group card · **least covered, like all drag and drop**
Make two groups and file a host into one. Drag a host card onto another group's heading; onto a host card
inside another group; and onto UNGROUPED.
Make two groups and file a host into one. Drag a host card up onto the other group's card.
**Pass:** whatever is under the pointer washes accent while the pointer is over it, the cursor shows a move
rather than a refusal, and the drop files the host — it moves under that heading and the counts on both
headings change. Dropping onto its own group's heading is refused while still in the air.
**Pass:** the group card under the pointer takes a two-pixel accent border while the pointer is over it, the
cursor shows a move rather than a refusal, and the drop files the host — the chip on its card changes to the
new group's name, the host counts under both group cards change, and the status line says where it went.
**Failure means:** headless Avalonia cannot synthesise a platform drag, so nothing about this gesture is
automated. The write it performs is: `MovingAHostToAGroup_FilesItAndLeavesItSelected`.
**Also check three refusals**, each of which must show the "no" cursor and mark nothing: over the card of the
group the host is *already* in; over another **host** card, which is deliberately not a target now that there
are no headings to say which group it would mean; and over the empty space around the cards.
**And getting a host back out** is the host's own editor — pick "No group" in its picker. There is no
UNGROUPED target on the desktop any more, because there is no UNGROUPED heading for it to be.
**And a drag held near the top or bottom edge of the grid scrolls it**, which is what makes this usable at
all with forty machines: the group cards are the first thing in the scroller, and a drag cannot use the
wheel. The pointer has to keep moving inside the band — a stationary pointer gets no drag events.
**Failure means:** headless Avalonia cannot synthesise a platform drag, so the picking up, the cursor and
the drop are covered by nothing. What *is* automated is the decision each drag event takes —
`HostGridTests.TheGroupCardsAreWhatAcceptsADroppedHost` raises a real `DragOver` over both kinds of card —
and the write at the end, `ShellFlowTests.MovingAHostToAGroup_FilesItAndLeavesItSelected`.
### 7.7 A click still selects, and a double click still connects
Click host cards; drag one a few pixels without releasing; double-click one. Then double-click a group
heading.
Click host cards; drag one a few pixels without releasing; double-click one. Then double-click the space
around the cards.
**Pass:** a click selects, a small movement starts nothing, and a double click connects. Double-clicking a
heading folds it and unfolds it again and connects to nothing.
**Pass:** a click selects, a small movement starts nothing, and a double click connects. Double-clicking the
space around the cards connects to nothing.
**Failure means:** the 5-pixel threshold in `HostsScreen.axaml.cs` is not doing its job — the same failure
as 2.16 on the other screen, and here it would make the grid unusable. A heading that connects means the
as 2.16 on the other screen, and here it would make the grid unusable. Empty space that connects means the
double-tap handler has lost its check that the pointer was over a card.
### 7.8 The highlight clears after a drag that goes nowhere
Drag a host over a heading and release outside the grid, or press Escape mid-drag.
Drag a host over a group card and release outside the grid, or press Escape mid-drag.
**Pass:** the wash goes away.
@@ -676,7 +687,7 @@ Drag a host over a heading and release outside the grid, or press Escape mid-dra
With host A selected, right-click host B and choose Delete.
**Pass:** the question names **B**. Then right-click a group heading.
**Pass:** the question names **B**. Then right-click the space around the cards.
**Pass:** no menu opens at all, and the host selection has not moved.