Merge branch 'main' into the Android head

Main grew the screens the host-management plan called for — hosts, pins, snippets, logs,
import, teams — plus the ObjectStore and Import projects behind two of them, and moved
WindowsDeviceKeyStore into the desktop head's Platform folder.

Five of those view models landed in a directory this branch had already moved, so they
join the rest in DodoSSH.Client.Shell: git spotted the rename and put them there, and the
namespaces followed. Shell picks up ObjectStore and Import as a result, which the Android
head then gets transitively and will use neither of at first — scoped storage means there
is no ~/.ssh/config to import, and file transfer is out of its first scope.

Desktop suites green at 155 and 64.
This commit is contained in:
2026-07-31 21:03:22 +02:00
199 changed files with 31294 additions and 775 deletions
+60 -8
View File
@@ -36,8 +36,10 @@
</Border>
<!--
One heading, for one vault. The chevron folds the list away; the count is the collection's own, so it
follows the filter without a second number to keep in step.
One heading, which names the vault while there is one and says ALL VAULTS once a team's is readable
too — a heading that went on naming the personal vault over a list containing a team's hosts would be
a quiet lie, so the rows carry the vault name instead. The chevron folds the list away; the count is
the collection's own, so it follows the filter without a second number to keep in step.
-->
<Button Grid.Row="1" Classes="flat grouphead" Command="{Binding ToggleHostsCommand}"
HorizontalAlignment="Stretch" HorizontalContentAlignment="Stretch">
@@ -65,10 +67,37 @@
-->
<ListBox Grid.Row="2" x:Name="HostList" Focusable="True"
IsVisible="{Binding AreHostsExpanded}"
ItemsSource="{Binding VisibleHosts}"
SelectedItem="{Binding SelectedHost}">
<ListBox.ItemTemplate>
<DataTemplate x:DataType="vm:HostRowViewModel">
ItemsSource="{Binding SidebarRows}"
SelectedItem="{Binding SelectedSidebarRow}">
<!--
Two kinds of row in one list, chosen by type. It has to be one ListBox: it owns the selection and it
is where keyboard focus lands when the terminal gives it back, neither of which survives a list per
group. A vault with no groups produces no heading rows at all, so this is the list it always was.
The heading is a row rather than a container, which means the control will happily select it. That is
turned back into the previous host selection in the view model — see SelectedSidebarRow — because
CONNECT, EDIT and DELETE all act on a host and a highlighted heading is not one.
-->
<ListBox.DataTemplates>
<DataTemplate DataType="vm:SidebarGroupHeader">
<Button Classes="flat grouphead" Command="{Binding $parent[ListBox].((vm:VaultViewModel)DataContext).ToggleGroupCommand}"
CommandParameter="{Binding}"
HorizontalAlignment="Stretch" HorizontalContentAlignment="Stretch">
<Grid ColumnDefinitions="Auto,*,Auto">
<TextBlock Grid.Column="0" Text="{Binding Chevron}" Foreground="{StaticResource TextFaint}"
FontSize="8" VerticalAlignment="Center" Margin="0,0,6,0" />
<TextBlock Grid.Column="1" Classes="label" Text="{Binding Label}"
Foreground="{StaticResource TextDim}" VerticalAlignment="Center"
TextTrimming="CharacterEllipsis" />
<TextBlock Grid.Column="2" Classes="mono" Text="{Binding Count}" FontSize="10"
Foreground="{StaticResource TextFaint}" VerticalAlignment="Center" />
</Grid>
</Button>
</DataTemplate>
<DataTemplate DataType="vm:HostRowViewModel">
<Grid ColumnDefinitions="Auto,Auto,*" Margin="0,5,10,5">
<!-- The accent strip a selected row carries; see the style in App.axaml. -->
@@ -102,11 +131,20 @@
-->
<TextBlock Classes="mono" Text="{Binding Authentication}" FontSize="9.5"
Foreground="{StaticResource TextFaint}" />
<!--
Which vault this host is in, and only when there is more than one to be in. It decides
who else can see the host and where an edit goes back to, so on a list that spans
several vaults it is not decoration.
-->
<TextBlock Classes="mono" Text="{Binding VaultBadge}" FontSize="9.5"
Foreground="{StaticResource TextFaint}"
IsVisible="{Binding HasVaultBadge}" />
</StackPanel>
</StackPanel>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox.DataTemplates>
</ListBox>
<!-- The editor doubles as the "add" form; there is no separate dialog. -->
@@ -148,6 +186,20 @@
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<!--
Which group this host is filed under. Inside the encrypted payload like everything else here, so
the server learns nothing about how the estate is organised — and a group the vault no longer has
keeps a placeholder entry, so that editing the port cannot quietly unfile the host.
-->
<ComboBox ItemsSource="{Binding EditorGroupChoices}"
SelectedItem="{Binding EditorSelectedGroup}"
HorizontalAlignment="Stretch">
<ComboBox.ItemTemplate>
<DataTemplate x:DataType="vm:GroupChoice">
<TextBlock Text="{Binding Label}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<CheckBox IsChecked="{Binding EditorRelayEnabled}"
Content="Connect through the server relay" />
<!--
@@ -193,7 +245,7 @@
-->
<Border Grid.Row="4" Padding="10,8" Background="{StaticResource DangerWash}"
BorderBrush="{StaticResource BorderSubtle}" BorderThickness="0,1,0,0"
IsVisible="{Binding IsConfirmingDeletion}">
IsVisible="{Binding IsConfirmingHostDeletion}">
<views:ConfirmDeleteCard />
</Border>