Give the keychain the DELETE its confirmation was already written for

The counted confirmation has been on this screen since the phone got its
screens, and nothing could raise it. PendingDeletion is armed by
DeleteSelectedItemCommand and its siblings, and the only Delete bindings
anywhere on this head were the files screen's remote delete and the snippets
one — so the card, and the header comment calling it structural, described a
question the phone could not ask. The button is what was missing rather than
anything behind it: the command, the counting and the wording were all already
shared with the desktop.

It sits in row 2 beside that confirmation rather than in a bar at the foot of
the screen, which is where HOSTS and FILES put what to do with the chosen thing.
ShowsItemActions is SelectedItemIsEditable and not-already-asking, so sharing
the row makes the question take the place of the button that asked it, and
DELETE cannot be pressed a second time underneath its own confirmation. A button
at the bottom and its question at the top would be one exchange drawn twice.

DELETE alone, without the EDIT the desktop's detail pane offers beside it,
because there is nothing on this head for EDIT to open. That is also why the
bar names the item rather than trusting the highlight: it is the only thing on
screen saying which row is about to go.

And the highlight had to be made to exist. HOSTS and FILES fill the selected
ContentPresenter, which works because their rows are bare Borders; a keychain
row is Border.card, and the card paints an opaque Sidebar over it. The selector
reaches the card itself instead, in the same Active the chosen host wears, so
the row a button now acts on looks different from the ones it does not.

The TAGS comment said renaming and deleting happened here. Half of that is true
as of this commit and the other half is not — a tag is still renamed on the
desktop, because no kind of item opens an editor on this screen.

The README's paragraph about what the phone is missing said there was no host
editor, which stopped being true when the floating + and both editors landed. It
now names what is actually absent: no local pane for file transfer, no keychain
editor, and no screen at all for pins, teams or import.
This commit is contained in:
2026-08-03 14:54:19 +02:00
parent 416f233657
commit 5800a4bbd0
2 changed files with 59 additions and 9 deletions
+7 -3
View File
@@ -280,9 +280,13 @@ screen exactly the width it was designed against.
File transfer **is** here now, in the shape scoped storage allows: one remote pane and the queue, over
either an SFTP host or a bucket. There is no local pane, because there is no browsable local filesystem to
put in one — moving a file *in* from the phone needs the system document picker and is the next piece of
work rather than a thing the screen pretends to do. What is still absent is a host editor and a keychain
item editor, so hosts and keys are created on the desktop and sync down; pins, teams and import have no
phone screen either. Importing an `~/.ssh/config` has no meaning on a phone at all.
work rather than a thing the screen pretends to do. Hosts and groups are made and corrected here now, from
a floating + on the Hosts screen, and both editors are cards in the list's own row rather than dialogs, so
the form never covers the thing it is about. The keychain has no editor of its own: SSH keys, passwords and
buckets are created on the desktop and sync down, and the phone will delete one — behind the same counted
confirmation — without offering to change it. The one item this head makes is a tag, from inside a host's
editor where tagging is what you were doing anyway; renaming one is still a desktop job. Pins, teams and
import have no phone screen either. Importing an `~/.ssh/config` has no meaning on a phone at all.
**Port forwarding is not built anywhere**, and the phone's More screen says so in a paragraph rather than
leaving a gap. The v2 design draws a whole screen for it; nothing in the SSH layer forwards anything, so
@@ -61,9 +61,10 @@
</RadioButton>
<!--
Tags. The one category holding nothing secret — a tag is a name — and here because renaming one is
one write instead of twenty, and a rename needs somewhere to happen. Making one usually happens in
a host's editor instead; this is where they are renamed and deleted.
Tags. The one category holding nothing secret — a tag is a name — and here because a host's editor
is the only place on this head that makes one, which leaves nowhere else to see the whole set or
what wears it. Deleting one happens here, with every other item kind; renaming is desktop work,
because nothing on this screen opens an editor.
-->
<RadioButton GroupName="section" Classes="chip" IsChecked="{Binding ShowsTags, Mode=OneWay}"
Command="{Binding ShowSectionCommand}"
@@ -93,11 +94,46 @@
-->
<TextBlock Grid.Row="1" Classes="detail" Margin="18,6,18,2" Text="{Binding SectionSummary}" />
<!-- ============ what to do with the chosen item ============ -->
<!--
DELETE and nothing beside it. The desktop's detail pane offers EDIT as well, and there is nothing here
for it to open — so this is the one action this head has for a keychain item, and until it existed the
confirmation below could not be reached at all.
It shares row 2 with that confirmation rather than being a bottom bar like the one on HOSTS or FILES,
because the question is what this button turns into: ShowsItemActions is false while a deletion is
armed, so the two swap in place and DELETE cannot be pressed again underneath its own question. A
button at the foot of the screen and its question at the top would be one exchange drawn twice.
And it names the item, because a keychain row is a card with a background of its own — see the selected
style below, which had nothing to colour until now. DELETE is the last button that should be pressed on
a guess about which row it means.
-->
<Border Grid.Row="2" IsVisible="{Binding ShowsItemActions}" Margin="12,4"
Background="{StaticResource Panel}" BorderBrush="{StaticResource BorderMid}"
BorderThickness="1" CornerRadius="12" Padding="14,10">
<Grid ColumnDefinitions="*,10,Auto">
<StackPanel Grid.Column="0" Spacing="3" VerticalAlignment="Center">
<TextBlock Classes="label" Text="SELECTED" />
<StackPanel Orientation="Horizontal" Spacing="7">
<TextBlock Classes="mono" FontSize="13" FontWeight="SemiBold"
Text="{Binding SelectedVaultItem.Name}" TextTrimming="CharacterEllipsis" />
<Border Classes="tag outline">
<TextBlock Text="{Binding SelectedVaultItem.Type}" />
</Border>
</StackPanel>
</StackPanel>
<Button Grid.Column="2" Classes="danger" Height="44" Width="104" Content="DELETE"
Command="{Binding DeleteSelectedItemCommand}" />
</Grid>
</Border>
<!-- ============ ◆ the counted confirmation ============ -->
<!--
In place, above the list it refers to. The text comes from the view model, which counts what breaks —
"three hosts authenticate with this key and will refuse to connect" — and the count is the whole
difference between a sentence somebody reads and one they click past.
In place, above the list it refers to and over the button that asked it. The text comes from the view
model, which counts what breaks — "three hosts authenticate with this key and will refuse to connect" —
and the count is the whole difference between a sentence somebody reads and one they click past.
-->
<Border Grid.Row="2" IsVisible="{Binding IsConfirmingDeletion}" Margin="12,4"
Background="{StaticResource DangerWash}" BorderBrush="{StaticResource DangerSoft}"
@@ -133,6 +169,16 @@
<Setter Property="Padding" Value="0" />
<Setter Property="MinHeight" Value="0" />
</Style>
<!--
The fill goes on the card and not on the ContentPresenter behind it, which is where HOSTS and
FILES put theirs. Those rows are bare Borders; a keychain row is Border.card, which paints an
opaque Sidebar over anything drawn underneath — so the selector has to reach the card itself or
the chosen row looks exactly like the others. It has to look different now that a button above
the list acts on it.
-->
<Style Selector="ListBoxItem:selected Border.card">
<Setter Property="Background" Value="{StaticResource Active}" />
</Style>
</ListBox.Styles>
<ListBox.ItemTemplate>
<DataTemplate x:DataType="vm:VaultItemRowViewModel">