Merge branch 'claude/main-page-group-hierarchy-3a3210'
ci / build and test (push) Successful in 1m27s
ci / android head (push) Failing after 5s
ci / api image (push) Successful in 21s

This commit is contained in:
2026-08-04 10:26:20 +02:00
8 changed files with 214 additions and 33 deletions
+38 -27
View File
@@ -16,6 +16,12 @@ Three constraints put things on this list, and they are worth knowing before add
Each item says what to do, what a pass looks like, and what a failure would mean.
**On the numbering.** A check keeps its number for life, because code comments and other documents cite them
`HostGridTests` sends a reader to 7.6, `platform-flags.md` to 3.63.8. A check inserted later therefore
takes a letter rather than pushing its neighbours along: 3.2a and 3.2b sit between 3.2 and 3.3 and always
will. Add in the same way, and keep each one next to the check it belongs beside; a gap in the numbers means
a phase had nothing left for a person to do, which is the good outcome rather than an omission.
---
## Phase 1 — the shell and the tab strip
@@ -290,11 +296,12 @@ surface. Use the ← button, or drag the file to the local pane first.
## Phase 3 — Host groups and snippets
Two synced item kinds, a sidebar that now draws headings, and one new frame between the host process and the
renderer. The data half of all of that is covered: the payloads round-trip, the server refuses the plaintext
fields, the sidebar's grouping and the snippet policy are in `ShellFlowTests`, and both new screens are
measured. What is left here is the part that only exists inside a WebView, plus the two-machine cases no
single-process test can reach.
Two synced item kinds, two ways of drawing a group — the desktop's grid of cards, one level at a time, and
the phone's flat list under headings — and one new frame between the host process and the renderer. The data
half of all of that is covered: the payloads round-trip, the server refuses the plaintext fields, both ways
of drawing a group and the snippet policy are in `ShellFlowTests`, and both new screens are measured. What is
left here is the part that only exists inside a WebView, plus the two-machine cases no single-process test
can reach.
### 3.1 A keychain with no groups looks exactly as it did
@@ -334,19 +341,6 @@ a filed host is worse than either: it is the box answering "no host matches that
keychain has got. 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.2b Making something while standing inside a group
Open a group, then press **+ NEW HOST**, and afterwards **+ NEW GROUP**.
**Pass:** the host editor opens with that group already chosen in its group picker, and the saved host is on
the screen it was made on rather than somewhere the trail is not. The group editor likewise opens with that
group as its parent, so the new group is a card inside the one that is open.
**Failure means:** anything created inside a group disappearing the moment it is saved. That is the papercut
a level-at-a-time grid comes with, and `NewHost` / `NewGroup` are where it is answered. Note the deliberate
difference between them: the host editor also takes a merely *selected* card as its group, the group editor
takes only the group that is open.
### 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.
@@ -361,17 +355,18 @@ Pressing the **middle** crumb goes back one level rather than all the way out.
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**
### 3.2b Making something while standing inside a group
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.
Open a group, then press **+ NEW HOST**, and afterwards **+ NEW GROUP**.
**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.
**Pass:** the host editor opens with that group already chosen in its group picker, and the saved host is on
the screen it was made on rather than somewhere the trail is not. The group editor likewise opens with that
group as its parent, so the new group is a card inside the one that is open.
**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`.
**Failure means:** anything created inside a group disappearing the moment it is saved. That is the papercut
a level-at-a-time grid comes with, and `NewHost` / `NewGroup` are where it is answered. Note the deliberate
difference between them: the host editor also takes a merely *selected* card as its group, the group editor
takes only the group that is open.
### 3.3 Deleting a group with hosts in it
@@ -394,6 +389,18 @@ the group picker shows "(a group that is no longer here)" and *keeps it selected
**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.
### 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.5 A grouped host stays editable on an older build · **needs two builds**
Only worth doing before a release that ships alongside an older client. A host filed into a group is written
@@ -753,7 +760,7 @@ wheel. The pointer has to keep moving inside the band — a stationary pointer g
**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`.
and the write at the end, `ShellFlowTests.MovingAHostToAGroup_FilesItAndTakesItOffTheLevelItCameFrom`.
### 7.7 A click still selects, and a double click still connects
@@ -804,6 +811,8 @@ add a bucket — then come back.
**Failure means:** the screen has gone back to copying the vault's lists once at unlock. Covered by
`TheTransfersScreen_FollowsTheVaultsHostList`; this is the version of it with a real picker in front of it.
---
## Phase 8 — Adding hosts on the phone
Nine checks, and the reason there are nine rather than none is worth stating: **the layout suite cannot see
@@ -902,6 +911,8 @@ Walk the sheet's two rows, the editor's fields, both pickers, the checkbox and t
**Failure means:** a control took its size from its glyph. The desktop suite's only tap-target assertion has
a floor of 20 pixels — set for a mouse — so copying it here would certify a target no thumb can hit.
---
## Phase 9 — Tag chips and the picker
The desktop half is measured by the layout suite, which now seeds three tags and puts all of them on one