Open a group by double-clicking it, and say where you are

ONE PRESS WAS DOING TWO JOBS. A group card was the only place a group could be
selected — it is what EDIT and DELETE aim at — and it was also the control that
narrowed the grid to that group. So there was no way to name a group in order to
rename it without every host outside it leaving the screen at the same moment,
and no way back except a SHOW ALL button that appeared beside the heading.

Two gestures instead. A click selects and does nothing else; a double-click
opens, which is what the host cards below already do to get a shell and what the
transfers screen's directories already do to go inside one. The grid now has one
vocabulary rather than one per list. The gesture is wired in the code-behind
beside the host one, and guarded the same way: a double-click on the space
around the cards must not open whichever group happened to be selected.

THE CARDS ARE ONE LEVEL NOW, not every group in the keychain. Groups nest, and
drawing all of them flat was the only honest thing to do while a card was a
filter — a filter nobody can see is a filter nobody can turn off. Once opening
one became navigation the cards became its contents, and VisibleGroups is that
level beside Groups the way VisibleHosts sits beside Hosts. Groups itself is
untouched: it is what every lookup reads and what the phone's headings are built
from, and the phone binds none of the new members.

Which is what the trail is for. A level with no name and no way out is a grid
that has quietly hidden things, so a breadcrumb sits above the cards — drawn
exactly as the transfers screen draws a directory path, same flat crumbs and
same separator, because it is the same control answering the same question and a
window with two breadcrumbs that look different has two ideas of what a path is.
The first crumb is always there and always goes back to every host, which is
what SHOW ALL was; that button went with it, because a control that only says
"stop" beside a trail that says where you are is a second control for one job,
and this one also gets you back one level rather than all the way.

EDIT AND DELETE AIM AT GroupTarget: the selected card, or the open group when no
card is selected. Without the fallback a group with nothing inside it could be
opened and then never edited, because opening a group is exactly what takes its
own card off the screen. It is also what a file manager does — act on the
selection, and on the current folder when there is none — and the pair is now
hidden with nothing to act on rather than shown doing nothing.

A DANGLING PARENT AND A CYCLE BOTH HAD TO END UP SOMEWHERE REACHABLE. Neither is
prevented: a parent id may point at a group deleted on another machine, and two
clients can each re-parent A under B and B under A while offline, which no merge
can see because the pointer is inside the payload. EffectiveParents promotes
both to the outermost level, which is the same degradation the resolver's
visited set produces for inheritance. The repair for either is the group's own
editor and the editor is opened from its card, so a group drawn nowhere would be
a broken state with the fix locked inside it.

Three tests in HostGridTests: the split rule through the properties the cards
bind, the pointer gesture itself in two windows so that "one press still only
selects" is asserted separately from the pair, and a nested group opened,
emptied of cards and walked back out of one level. The last presses the trail as
it is actually rendered rather than calling the command, because a crumb reaches
the vault through a $parent binding — a string that compiles whether or not it
resolves, and would otherwise leave a row of buttons that do nothing. 85 layout
tests and 234 shell-flow tests pass.

Manual-checks 3.2, 7.6 and 7.7 follow the new gestures, and 3.2a and 3.4a are
new: nesting, and the two states above, both of which need two machines and
neither of which headless Avalonia can reach.
This commit is contained in:
2026-08-03 16:16:18 +02:00
parent fd8497bb76
commit 0c0ac94312
6 changed files with 549 additions and 80 deletions
+3 -2
View File
@@ -101,8 +101,9 @@ the chrome, hosts and terminals, file transfer, the vault, teams, and preference
> **The hosts screen became a grid of cards** — groups above, hosts below — and the 268-pixel host sidebar
> went with it. That column was choosing among forty machines *and* editing one of them at two-thirds
> width; the grid took the first job at full width and a 304-pixel right-hand drawer took the second. The
> drawer collapses when nothing is selected, which is most of the time. Pressing a group card narrows the
> grid to that group; `SHOW ALL` is the way back.
> drawer collapses when nothing is selected, which is most of the time. Pressing a group card selects it;
> double-pressing one opens it, and a breadcrumb trail above the cards is where you are and the way back
> the same trail the transfers screen draws over a directory.
>
> **The type scale went up a point and the text ramp went white.** `#E3E7F4` was a blue-tinted white on
> blue-black surfaces, which costs contrast twice — once for being darker than white and once for sharing
+41 -4
View File
@@ -283,11 +283,42 @@ Make two groups and file some hosts into each through the host editor.
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.
**Then press a group card once.** It is marked as chosen and **nothing else happens** — the grid still holds
every host, and EDIT and DELETE now aim at that group. **Then double-press it.** The group opens: the hosts
narrow to the ones filed under it, the trail above the cards reads `ALL HOSTS <name> `, and the card grid
shows what is *inside* that group rather than every group in the keychain. Pressing ALL HOSTS brings the rest
back.
**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`.
**Failure means:** if one press still narrows the grid, the card `ListBox` is bound to the wrong property —
`SelectedItem` is `SelectedGroup`, and only `OpenGroupCommand` writes `GroupFilter`. 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.2a A group inside a group, and the way back out
Make two groups and file one under the other with the parent picker in the group editor.
**Pass:** only the outer group has a card to start with. Double-press it and the inner one is the only card
shown, with the trail reading `ALL HOSTS <outer> `. Double-press that, and the cards disappear entirely —
it has nothing inside it — while the trail, EDIT and DELETE stay: with no card selected the two buttons act
on the group the trail ends with, so a group with nothing in it can still be renamed after being opened.
Pressing the **middle** crumb goes back one level rather than all the way out.
**Failure means:** cards for groups that are not at this level is `VisibleGroups` having been bound past —
the flat `Groups` is the phone's and the lookups'. A group that cannot be reached at all is worse and is the
case `EffectiveParents` promotes: see 3.4a.
### 3.4a A parent that dangles, or a cycle · **needs two machines**
Rare, and the reason it is here is that the repair for both is inside the group's own editor, which is
opened from its card. On A, file group X under group Y and sync. On B, sync, delete Y, sync. Back on A, sync.
For the cycle: with both machines offline, file X under Y on A and Y under X on B, then sync both.
**Pass:** in both cases every group still has a card at the outermost level, so every one of them can be
opened and its parent cleared. Nothing is missing from the grid, and the trail never loops.
**Failure means:** a group drawn nowhere is a group that can never be repaired — the fix is locked inside the
state that needs fixing. See `VaultViewModel.EffectiveParents`.
### 3.3 Deleting a group with hosts in it
@@ -653,6 +684,9 @@ new group's name, the host counts under both group cards change, and the status
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.
**The targets are the cards on screen, which are one level** — see 3.2a. Filing into a group nested under
another means opening the outer one first, exactly as moving a file into a subfolder does.
**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.
@@ -677,6 +711,9 @@ space around the cards connects to nothing.
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.
**And the same two gestures on the group cards above**, where they mean select and open rather than select
and connect — the space around *those* cards must open nothing, for the same reason. See 3.2.
### 7.8 The highlight clears after a drag that goes nowhere
Drag a host over a group card and release outside the grid, or press Escape mid-drag.