Put a host inside the group it is filed under, rather than beside it

The group cards were headings with a navigation gesture bolted on. Opening one
narrowed the grid to its hosts, but the level above already held every host in
the keychain — so a card could only ever subtract, filing something changed
nothing but a chip, and a keychain with forty machines was forty cards however
carefully anybody had arranged them. The trail said ALL HOSTS and meant it.

So the grid holds one level of the tree, the way a directory pane holds one
directory. A host filed under a group is inside that group and is not also on
the screen the group's card sits on; the outermost level is what nothing has
been filed into. A group is a place now, and the cards, the trail and the drop
target were already the vocabulary for saying so.

The find box is the exception and had to be one. Typed into, it searches the
open group and everything under it, which from the outermost level is every
machine in the keychain. A box scoped to the level it was typed on would answer
"no host matches that" about a host this keychain has got, and finding a machine
without first remembering where it was filed is most of what the box is for.
The accent chip on a card is what tells a searched-up host from one that lives
at this level.

Two things came free with the change and are handled rather than left.

The phone would have broken. Its list binds SidebarRows, which was a projection
over VisibleHosts, and it has no group cards and nowhere to open one into — so
level-scoping would have left it drawing only the hosts nobody had filed.
RebuildSidebarRows takes its own pass over the hosts now, narrowed by the vault
switches and the box and by nothing else, which is the whole tree flattened
under headings: exactly what it drew before.

And anything created inside a group disappeared the moment it was saved. The
host editor opens on the group the screen is about rather than only on a
selected card, and + NEW GROUP defaults its parent to the group that is open.
Deliberately not the selected card there: a highlighted card is what EDIT and
DELETE are aimed at, and reading it as "and the next group goes inside it" would
nest one because somebody had clicked something. The host editor takes both
because it always took the selection, and its picker shows the answer before
anything is written.

Dropping a host on a group card now takes the card off the grid, and the
selection goes with it — Connect, Edit and Delete all read that property and
none of them should be aimed at a card that has left the screen. The status line
is what says where it went, which is why the manual check now asks for it to be
read. Coming back out through the editor lands the host on this level again, and
there the selection survives.

An empty grid has two more things it can say: that every host is filed away,
which the level-at-a-time grid made reachable and which is not the same sentence
as "there are none", and that nothing under this group matches what was typed —
with ALL HOSTS named as the way to widen it.

Not changed, and next door: a group card counts the hosts filed directly under
it, so a group holding only subgroups reads "0 hosts". That was already true and
is more visible now that its subgroups' hosts are not spilled onto the level
above.

1522 tests pass. Four are new — the level rule and the phone's flat list
asserted together, the box reaching two levels down, the all-filed sentence, and
where a host and a group made inside a group end up. MovingAHostToAGroup was
asserting the old outcome and is rewritten rather than adjusted: it held that
the host stayed selected, and what it holds now is that the host leaves the
level it came from.
This commit is contained in:
2026-08-04 09:51:55 +02:00
parent e82a25c912
commit 2a56ae3efb
6 changed files with 368 additions and 53 deletions
+16 -1
View File
@@ -52,6 +52,12 @@
connect path, the selection and the pinned host key list all read the unfiltered collection — so a
filter left in the box can hide a host but can never break one.
◆ IT IS ALSO THE WAY OUT OF THE GROUPS. The grid below holds one level of the tree, and typing here
searches that level and everything under it — every machine in the keychain while nothing is open.
That is the one thing on this screen that crosses a group boundary, and it has to be: a search that
looked only where you happen to be standing would say "no host matches that" about a host this
keychain has got. See VaultViewModel.Matches.
Ctrl+K is named on it because the palette is the other way to reach a host by typing, and somebody
who has found this box should know about the one that also connects on Enter.
@@ -341,8 +347,17 @@
taken back out of a group through its own editor, which is the one place "no group" can be said
plainly.
◆ ONE LEVEL, AS A DIRECTORY PANE IS. A host filed under a group is inside that group and is
not also here — the cards above are the way into it, and this grid holds what is loose at
whatever level the trail names. It used to hold every host at once with the groups drawn as
headings over them, which made opening a group an operation that could only ever subtract.
See VaultViewModel.Matches, and the note on the find box above for the one thing that crosses
a group boundary.
Which group a card is in is on the card, as a chip. That is what the heading above it used to
say, and one chip per card says it without an accordion.
say, and one chip per card says it without an accordion — and it is what tells a search result
from a host that lives at this level, since searching is the one way a card from inside a
group reaches this grid.
SelectedItem is still SelectedSidebarRow and deliberately not SelectedHost, though every item
here is now a host. That property swallows the null the ListBox writes back whenever the