Merge branch 'claude/vault-key-sync-sharing-d098aa'
ci / build and test (push) Successful in 2m0s
ci / android head (push) Successful in 3m21s
ci / desktop nightly (push) Successful in 41s
ci / api image (push) Successful in 33s

This commit is contained in:
2026-08-06 07:39:32 +02:00
10 changed files with 1223 additions and 32 deletions
@@ -712,6 +712,25 @@
</ComboBox>
<TextBlock Classes="body"
Text="The host is re-encrypted with the other vault's key, so everybody who holds that key can read it and nobody else can. Its group and tags stay behind — both belong to the vault it is leaving." />
<!--
◆ THE KEY. A binding resolves across vaults, so the host keeps working here whichever way this is
answered — but the vault it has just joined holds one key, and a host whose own key stayed behind
is one its new colleagues cannot connect with.
Unticked, because moving a key into a team's vault hands it to everybody who holds that vault's
key: a disclosure is chosen, never defaulted into. The line under it is the count of what else
uses that key, which is the difference between an obvious yes and an obvious no.
-->
<CheckBox IsChecked="{Binding BringsTheBindingAlong}" MinHeight="44"
IsVisible="{Binding HasABindingToBring}">
<TextBlock Classes="mono" FontSize="11.5" TextWrapping="Wrap"
Text="{Binding BindingToBringQuestion}" />
</CheckBox>
<TextBlock Classes="body" TextWrapping="Wrap"
IsVisible="{Binding HasABindingToBring}"
Text="{Binding BindingToBringNote}" />
<Grid ColumnDefinitions="*,8,*">
<Button Grid.Column="0" Classes="primary" Height="44" Content="MOVE"
Command="{Binding ConfirmMoveHostCommand}" IsEnabled="{Binding !IsBusy}" />
@@ -149,8 +149,19 @@
</StackPanel>
</StackPanel>
<Button Grid.Column="2" Classes="danger" Height="44" Width="104" Content="DELETE"
Command="{Binding DeleteSelectedItemCommand}" />
<!--
MOVE beside it, and only where there is somewhere to move to — the rule the host's MOVE follows on
this head, for the reason a phone has: there is no room to draw a button that answers with a
refusal. It is the ghost of the pair rather than the danger one, because a move is undone by
moving it back.
-->
<StackPanel Grid.Column="2" Orientation="Horizontal" Spacing="8">
<Button Classes="secondary" Height="44" Width="86" Content="MOVE"
IsVisible="{Binding CanMoveSelectedItem}"
Command="{Binding MoveSelectedItemCommand}" />
<Button Classes="danger" Height="44" Width="104" Content="DELETE"
Command="{Binding DeleteSelectedItemCommand}" />
</StackPanel>
</Grid>
</Border>
@@ -180,6 +191,47 @@
</StackPanel>
</Border>
<!-- ============ ◆ moving it to another vault ============ -->
<!--
The desktop's panel, in the place the deletion question uses and never at the same time as it: MOVE
disarms a pending deletion on the way in, and the buttons that ask either question are hidden while
one is up.
Both sentences are here rather than only in the status line afterwards, which on a phone is one line at
the bottom of a screen somebody has already navigated away from. The second one is the count of what
points at this key — every one of them is re-aimed at the vault it moves to, and that is the part
nobody can see from a keychain row.
-->
<Border Grid.Row="3" IsVisible="{Binding IsMovingItem}" Margin="12,4"
Background="{StaticResource Panel}" BorderBrush="{StaticResource BorderMid}"
BorderThickness="1" CornerRadius="12" Padding="14,12">
<StackPanel Spacing="8">
<TextBlock Classes="label" Text="MOVE TO VAULT" />
<TextBlock Classes="mono" FontSize="12" TextWrapping="Wrap"
Text="{Binding MovingItemLabel}" />
<ComboBox HorizontalAlignment="Stretch" MinHeight="44"
ItemsSource="{Binding MoveItemVaultChoices}"
SelectedItem="{Binding SelectedMoveItemVault}">
<ComboBox.ItemTemplate>
<DataTemplate x:DataType="vm:VaultChoiceViewModel">
<TextBlock Classes="mono" FontSize="12" Text="{Binding Display}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<TextBlock Classes="body" TextWrapping="Wrap"
Text="It is re-encrypted with the other vault's key, so everybody who holds that key can read it and nobody in the vault it leaves can." />
<TextBlock Classes="body" TextWrapping="Wrap"
IsVisible="{Binding HasMovingItemUsage}"
Text="{Binding MovingItemUsage}" />
<Grid ColumnDefinitions="*,8,*" Margin="0,4,0,0">
<Button Grid.Column="0" Classes="primary" Height="44" Content="MOVE"
Command="{Binding ConfirmMoveItemCommand}" IsEnabled="{Binding !IsBusy}" />
<Button Grid.Column="2" Classes="secondary" Height="44" Content="CANCEL"
Command="{Binding CancelMoveItemCommand}" />
</Grid>
</StackPanel>
</Border>
<!-- ============ the items ============ -->
<Panel Grid.Row="4">