Let the phone delete a host, and move or remove a group

The desktop gained three things the phone did not follow: moving a group to
another vault, the second question asking whether a group's deletion takes its
hosts with it, and — since long before either — deleting a host at all. What
that left was a head whose v3 + can fill a keychain and whose editors can
correct one, with no way to empty either.

The commands could not simply be bound. DeleteGroup and MoveGroup aim at
GroupTarget, which is the selected card or the open group, and the phone has
neither: its list draws headings, and a heading's selection deliberately bounces
back to the host. Called bare on that head they would have returned having done
nothing — a DELETE that appears to have been pressed and has not. Both now take
the row and fall back to GroupTarget for the desktop's menu, and
ConfirmMoveGroupAsync resolves from the panel's own movingGroupId rather than
from the selection, which is also the honester answer on the desktop: what moves
is the shelf the panel was opened on.

The heading's pencil became a menu. Three icons after a chevron, a name, a vault
badge and a count is what would be left of the name at 360dp, so the ⋯ raises
the add sheet's shape carrying Edit, Move to another vault, a rule, and Delete —
the desktop's card menu, in the one idiom this screen already has. It does not
carry Open: the desktop's grid holds one level of the group tree and this list
holds all of it flattened, so there is nowhere to open a group into.

DELETE under a host sits on a row of its own beneath EDIT and MOVE rather than
beside them. A phone has no hover and no tooltip, so where a thumb lands is the
only thing separating a destructive control from an ordinary one. Both questions
take the controls that asked them — ShowsConnectControls, which is the phone's
half of the rule ShowsHostPaneActions already carries for the desktop's drawer —
so DELETE cannot be pressed a second time underneath its own confirmation.

Preferences gained the running version, and the sentence saying this head does
not replace itself and that no DodoSSH server will ever offer one. It reads
Updates.CurrentVersion off the same view model the desktop's UPDATES section
does, over the null channel that reports itself unsupported.

Nothing was needed for the realtime push: it is composed in ServerConnection,
which both heads use.

Seven tests, all phone-shaped — a group acted on with nothing selected, the menu
waved away leaving nothing armed, the ungrouped heading raising none, and the
bar's three states. The rectangles remain unmeasurable for the reason phase 8
gives; the checks for them are 8.10 to 8.13 and 13.6.
This commit is contained in:
jaap-jan committed 2026-08-04 19:58:10 +02:00
1 parent e923b12b7f
commit 50fa6fba38
10 files changed
+814 -52

No files matched your search

@@ -81,6 +81,37 @@
<TextBlock Classes="body" Margin="6,12,6,0" TextAlignment="Center"
Text="{Binding StatusMessage}" />
<!-- ============ ◆ which build this is ============ -->
<!--
A fact, printed, in the shape ACCOUNT uses on the desktop's screen — and the same string the desktop
prints under UPDATES, read off the same place. It comes from the assembly's own informational
version, so it answers on a build run from a checkout as well as on a released one; see
ClientVersion.Current.
It is worth a row of its own on the head that has no updater. On the desktop the version sits beside
a CHECK NOW that will tell you whether it is current; here it is the only answer, and it is the one
thing to read out when somebody asks which build is misbehaving. The sentence under it says where a
newer one comes from, which is ADR 0011 rule 2 in plain words: never from the server you sign in to.
Bound through Updates, which is the shell's update view model and exists on this head too — over the
null channel, so it reports itself unsupported and its loop never starts. That is why there is no
button here rather than a disabled one. See ADR 0013.
-->
<TextBlock Classes="section" Text="THIS BUILD" Margin="6,26,0,0" />
<Border Classes="card" Margin="0,10,0,0">
<StackPanel Spacing="10">
<Grid ColumnDefinitions="Auto,*">
<TextBlock Grid.Column="0" Classes="label" Text="VERSION" VerticalAlignment="Center" />
<TextBlock Grid.Column="1" Classes="mono" FontSize="13" Margin="10,0,0,0"
HorizontalAlignment="Right" Text="{Binding Updates.CurrentVersion}" />
</Grid>
<TextBlock Classes="body" IsVisible="{Binding Updates.IsUnsupported}"
Text="This head does not replace itself. A newer DodoSSH is an APK from the project's own release page, installed the way this one was — and no DodoSSH server will ever offer you one, whatever it says. A server that does is not a server to trust." />
</StackPanel>
</Border>
<TextBlock Classes="section" Text="NOT HERE" Margin="6,26,0,0" />
<Border Classes="card" Margin="0,10,0,0">