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
@@ -689,6 +689,26 @@
</ComboBox>
<TextBlock Classes="hint" FontSize="10.5" TextWrapping="Wrap"
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, WHICH IS THE HALF THE SENTENCE ABOVE CANNOT PROMISE. A binding resolves across
vaults, so the host goes on working here either way — but the colleagues it has just joined
hold one vault's key, and a host whose key stayed behind is one they cannot connect with.
Unticked, and it has to be: moving a key into a team's vault hands it to everybody who holds
that key. The note under it is the count, because a key twenty machines authenticate with is a
different decision from one nothing else uses, and neither is visible from here otherwise.
-->
<CheckBox IsChecked="{Binding BringsTheBindingAlong}"
IsVisible="{Binding HasABindingToBring}"
ToolTip.Tip="Moves the key or password itself into the same vault, and re-aims every host and group that used it at where it has gone.">
<TextBlock Text="{Binding BindingToBringQuestion}" Classes="hint" FontSize="12"
TextWrapping="Wrap" />
</CheckBox>
<TextBlock Classes="hint" FontSize="10.5" TextWrapping="Wrap"
IsVisible="{Binding HasABindingToBring}"
Text="{Binding BindingToBringNote}" />
<StackPanel Orientation="Horizontal" Spacing="6">
<Button Classes="accent" Content="MOVE" Command="{Binding ConfirmMoveHostCommand}"
IsEnabled="{Binding !IsBusy}" />
@@ -297,9 +297,50 @@
<StackPanel Orientation="Horizontal" Spacing="6" Margin="0,14,0,0"
IsVisible="{Binding ShowsItemActions}">
<Button Classes="ghost" Content="EDIT" Command="{Binding EditSelectedItemCommand}" />
<!--
Only where there is somewhere to move to, unlike EDIT beside it, which is the same rule the
host's MOVE follows on the phone: a button that answers with "this is the only vault you can
write to" is a button that should not have been drawn. Keys and passwords only — a tag and a
bucket are read from the active vault alone, so "another vault" is not a question they have.
-->
<Button Classes="ghost" Content="MOVE" Command="{Binding MoveSelectedItemCommand}"
IsVisible="{Binding CanMoveSelectedItem}"
ToolTip.Tip="Re-encrypts this under another vault's key, and re-aims every host and group that used it at where it has gone." />
<Button Classes="danger" Content="DELETE" Command="{Binding DeleteSelectedItemCommand}" />
</StackPanel>
<!--
◆ MOVING THE ITEM TO ANOTHER VAULT, in the place those buttons were. The host's panel, over
here — see HostDrawer.axaml — and what it is for is the thing a shared vault could not do until
now: a key typed into a personal vault before the team existed was stuck there, and the only
way across was to paste the private half into a second item and delete the first.
The two sentences under the picker are the whole of the decision. The first says what a move
is; the second says what points at this key, because everything that does is re-aimed at it in
its new vault and somebody moving a key twenty machines use should see the twenty first.
-->
<StackPanel Spacing="8" Margin="0,14,0,0" IsVisible="{Binding IsMovingItem}">
<TextBlock Classes="label" Text="MOVE TO VAULT" />
<ComboBox HorizontalAlignment="Stretch" ItemsSource="{Binding MoveItemVaultChoices}"
SelectedItem="{Binding SelectedMoveItemVault}">
<ComboBox.ItemTemplate>
<DataTemplate x:DataType="vm:VaultChoiceViewModel">
<TextBlock Text="{Binding Display}" FontSize="12" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<TextBlock Classes="hint" FontSize="10.5" 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="hint" FontSize="10.5" TextWrapping="Wrap"
IsVisible="{Binding HasMovingItemUsage}"
Text="{Binding MovingItemUsage}" />
<StackPanel Orientation="Horizontal" Spacing="6">
<Button Classes="accent" Content="MOVE" Command="{Binding ConfirmMoveItemCommand}"
IsEnabled="{Binding !IsBusy}" />
<Button Classes="ghost" Content="CANCEL" Command="{Binding CancelMoveItemCommand}" />
</StackPanel>
</StackPanel>
<!--
The public half only, and there is no button for the other one. Installing a key means pasting
this line into a host's authorized_keys; a private key on the clipboard is a private key in