Public Access
Let a host be moved to another vault
The one thing the host editor's vault picker has always been unable to offer, and the comment beside it said so: an existing host's vault was not a field because the two vaults are encrypted under different keys. That is still true. What changed is that it is no longer a reason to have nothing. **A move is a copy and a tombstone, and it cannot be anything else.** A payload is sealed under its vault's key and its AAD binds the vault, the entity id and the item version, so no edit moves one and no server call could — the server holds ciphertext it cannot read. What crosses is the plaintext, in this process, between an unwrap under one key and a seal under another. VaultItemRepository gained MoveAsync for it, so the three decisions below live in one place with their reasons rather than being re-derived at each call site. The item takes a new id. Keeping it would put one entity id in two vaults, and the item table is keyed on the type and the id rather than on the vault — so the destination's row and the source's tombstone would be the same row, and the move would delete what it had just written. The write comes first and the tombstone second, which decides what an interruption leaves: a copy in both vaults, visible and deletable, rather than a tombstone with nothing on the other side. Both are queued rather than sent, so the window is a crash between two local writes; it is still worth being on the survivable side of. Two activity lines rather than one, because that is what the two vaults actually record. A single "moved" line would have to be written to one of them and would be missing from the other's history. **The group and the tags stay behind, and that is the half that makes this honest.** Both are items of the vault the host is leaving: the editor's group picker offers one vault's groups and the chips are drawn from one vault's tags. A host carrying either across would resolve it on the machine that moved it — groups and tags are resolved over every readable vault — and dangle for everybody else in the destination. The mover and their colleagues would be looking at two different hosts. Cleared and reported beats carried and invisible. The key or password binding is kept, and the difference is not inconsistency. Those genuinely resolve across vaults — one key on twenty hosts in three vaults is the arrangement they exist for — so clearing them would take a working host and make one that cannot connect. What the message does instead is name a binding that is now outside the destination, because that is precisely what the other members of it will not be able to resolve. **It is not in the editor**, on either head: the desktop puts it in the detail pane's ⋯ menu above the separator Delete sits below, and the phone beside EDIT. A picker inside the form would move a machine as a side effect of correcting a port, which is the bug the editor's own vault picker was fenced off to prevent in the first place. The panel takes the footer as the deletion question does, and says what will be left behind before the tap rather than after it — on a phone, where the status line afterwards is one line on a screen somebody has already navigated away from, that is the only place it reliably gets read. The phone hides the button where there is nowhere to go rather than offering one that answers with a refusal; the desktop keeps its menu entry either way, because a menu that grew and shrank would be a menu whose items move. One thing found while writing the test and deliberately not changed. The pass that follows every write on this screen reports what it moved and supersedes the confirmation — for a save and a delete as much as for a move — so the move's own sentence is what somebody sees offline. The test asserts it in that state and says why. Making confirmations survive their own sync pass is a question about the whole screen rather than about this. Four places said an item could never be moved, two of them sentences on screen in both heads. All four now say what is true, including the design gaps document, where the chevron beside the vault name stays undrawn for a different reason: a chevron on a subtitle implies an edit, and this is a re-seal, a new id and two references left behind.
This commit is contained in:
@@ -262,10 +262,12 @@
|
||||
|
||||
<!--
|
||||
◆ WHICH VAULT THIS HOST WILL LIVE IN. Drawn only while adding and only where there is more than
|
||||
one vault that can be written to, exactly as on the desktop — an existing host's vault cannot
|
||||
change, because the two are encrypted under different keys and moving an item is a delete and a
|
||||
retype. Above GROUP rather than below it because it decides what GROUP can offer: a group is an
|
||||
item in one vault, so choosing a vault refills that list with that vault's groups.
|
||||
one vault that can be written to, exactly as on the desktop — an existing host's vault is not a
|
||||
field of this form, because the two are encrypted under different keys and changing it is a
|
||||
re-seal into one vault and a tombstone in the other. That is MOVE, beside EDIT under the host,
|
||||
and it is separate so that it cannot happen as a side effect of saving something else. Above
|
||||
GROUP rather than below it because it decides what GROUP can offer: a group is an item in one
|
||||
vault, so choosing a vault refills that list with that vault's groups.
|
||||
-->
|
||||
<StackPanel Spacing="6" IsVisible="{Binding ShowsEditorVaultChoice}">
|
||||
<TextBlock Classes="label" Text="VAULT" Margin="0,4,0,0" />
|
||||
@@ -279,7 +281,7 @@
|
||||
</ComboBox.ItemTemplate>
|
||||
</ComboBox>
|
||||
<TextBlock Classes="body"
|
||||
Text="A host in a shared vault is readable by everybody holding that vault's key, and it cannot be moved out afterwards." />
|
||||
Text="A host in a shared vault is readable by everybody holding that vault's key. It can be moved out later, with MOVE under the host — what it cannot do is become unreadable to somebody who has already synced it." />
|
||||
</StackPanel>
|
||||
|
||||
<TextBlock Classes="label" Text="GROUP" Margin="0,4,0,0" />
|
||||
@@ -432,45 +434,97 @@
|
||||
BorderThickness="0,1,0,0" Padding="14,12">
|
||||
<StackPanel Spacing="10">
|
||||
|
||||
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||
<TextBlock Classes="label" Text="CONNECT TO" />
|
||||
<TextBlock Classes="mono" FontSize="11" Text="{Binding SelectedHost.Label}" />
|
||||
</StackPanel>
|
||||
|
||||
<!--
|
||||
Shown only for a host that actually asks for one. A password box beside a key-authenticated host
|
||||
is an invitation to type a secret nothing will use.
|
||||
|
||||
The tick below it is the phone's whole answer to storing one, and on this head it is the only one:
|
||||
the keychain lists credentials here but has no editor to create one in, so before this a password
|
||||
typed on a phone could only ever be typed again. The host editor's picker could then bind it.
|
||||
Everything about connecting, in one group so that the move panel below can take the bar rather
|
||||
than appear underneath it. A password box and a CONNECT button under a form asking which vault to
|
||||
move the host into would be two unrelated questions in one bar, and the taller of the two would
|
||||
push the other off a phone screen.
|
||||
-->
|
||||
<TextBox Classes="field secret" IsVisible="{Binding SelectedHostAsksForAPassword}"
|
||||
Text="{Binding ConnectPassword}" PlaceholderText="password">
|
||||
<TextBox.KeyBindings>
|
||||
<KeyBinding Gesture="Enter" Command="{Binding ConnectCommand}" />
|
||||
</TextBox.KeyBindings>
|
||||
</TextBox>
|
||||
<StackPanel Spacing="10" IsVisible="{Binding !IsMovingHost}">
|
||||
|
||||
<CheckBox IsChecked="{Binding RemembersConnectPassword}" MinHeight="44"
|
||||
IsVisible="{Binding SelectedHostAsksForAPassword}">
|
||||
<TextBlock Classes="mono" FontSize="11.5" TextWrapping="Wrap"
|
||||
Text="Remember this password for this host" />
|
||||
</CheckBox>
|
||||
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||
<TextBlock Classes="label" Text="CONNECT TO" />
|
||||
<TextBlock Classes="mono" FontSize="11" Text="{Binding SelectedHost.Label}" />
|
||||
</StackPanel>
|
||||
|
||||
<TextBlock Classes="detail" TextWrapping="Wrap" IsVisible="{Binding !SelectedHostAsksForAPassword}"
|
||||
Text="{Binding SelectedHostAuthenticationNote}" />
|
||||
<!--
|
||||
Shown only for a host that actually asks for one. A password box beside a key-authenticated host
|
||||
is an invitation to type a secret nothing will use.
|
||||
|
||||
<Button Classes="primary" Content="CONNECT" Command="{Binding ConnectCommand}"
|
||||
IsEnabled="{Binding !IsBusy}" />
|
||||
The tick below it is the phone's whole answer to storing one, and on this head it is the only
|
||||
one: the keychain lists credentials here but has no editor to create one in, so before this a
|
||||
password typed on a phone could only ever be typed again. The host editor's picker could then
|
||||
bind it.
|
||||
-->
|
||||
<TextBox Classes="field secret" IsVisible="{Binding SelectedHostAsksForAPassword}"
|
||||
Text="{Binding ConnectPassword}" PlaceholderText="password">
|
||||
<TextBox.KeyBindings>
|
||||
<KeyBinding Gesture="Enter" Command="{Binding ConnectCommand}" />
|
||||
</TextBox.KeyBindings>
|
||||
</TextBox>
|
||||
|
||||
<CheckBox IsChecked="{Binding RemembersConnectPassword}" MinHeight="44"
|
||||
IsVisible="{Binding SelectedHostAsksForAPassword}">
|
||||
<TextBlock Classes="mono" FontSize="11.5" TextWrapping="Wrap"
|
||||
Text="Remember this password for this host" />
|
||||
</CheckBox>
|
||||
|
||||
<TextBlock Classes="detail" TextWrapping="Wrap"
|
||||
IsVisible="{Binding !SelectedHostAsksForAPassword}"
|
||||
Text="{Binding SelectedHostAuthenticationNote}" />
|
||||
|
||||
<Button Classes="primary" Content="CONNECT" Command="{Binding ConnectCommand}"
|
||||
IsEnabled="{Binding !IsBusy}" />
|
||||
</StackPanel>
|
||||
|
||||
<!--
|
||||
Not asked for by the design, and here because a + that adds hosts with no way to correct one is a
|
||||
strange thing to ship. It costs nothing: the editor above serves both, so this is the same panel
|
||||
opened on an existing row.
|
||||
|
||||
MOVE is beside it rather than inside it, and that is the same line the desktop's menu draws: which
|
||||
vault a host is in is not a field of the host. The two vaults are encrypted under different keys,
|
||||
so it is a re-seal into one and a tombstone in the other — nothing a SAVE could do. It shows only
|
||||
where there is somewhere to move to; see VaultViewModel.CanMoveSelectedHost.
|
||||
-->
|
||||
<Button Classes="secondary" Height="44" Content="EDIT"
|
||||
Command="{Binding EditSelectedHostCommand}" />
|
||||
<Grid ColumnDefinitions="*,8,*" IsVisible="{Binding !IsMovingHost}">
|
||||
<Button Grid.Column="0" Classes="secondary" Height="44" Content="EDIT"
|
||||
Command="{Binding EditSelectedHostCommand}" />
|
||||
<Button Grid.Column="2" Classes="secondary" Height="44" Content="MOVE"
|
||||
IsVisible="{Binding CanMoveSelectedHost}"
|
||||
Command="{Binding MoveHostCommand}" />
|
||||
</Grid>
|
||||
|
||||
<!--
|
||||
◆ MOVING THE HOST TO ANOTHER VAULT, in the place the connect controls were. A picker and two
|
||||
buttons rather than a question with a yes: what is being asked is which vault, and a move is
|
||||
undone by moving it back.
|
||||
|
||||
The sentence is not decoration. A group and a tag are items of the vault the host is leaving, so
|
||||
neither can come with it — and on a phone, where the status line afterwards is one line at the
|
||||
bottom of a screen somebody has already navigated away from, saying it before the tap is the only
|
||||
place it reliably gets read.
|
||||
-->
|
||||
<StackPanel Spacing="10" IsVisible="{Binding IsMovingHost}">
|
||||
<TextBlock Classes="label" Text="MOVE TO VAULT" />
|
||||
<ComboBox HorizontalAlignment="Stretch" MinHeight="44"
|
||||
ItemsSource="{Binding MoveVaultChoices}"
|
||||
SelectedItem="{Binding SelectedMoveVault}">
|
||||
<ComboBox.ItemTemplate>
|
||||
<DataTemplate x:DataType="vm:VaultChoiceViewModel">
|
||||
<TextBlock Classes="mono" FontSize="12" Text="{Binding Display}" />
|
||||
</DataTemplate>
|
||||
</ComboBox.ItemTemplate>
|
||||
</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." />
|
||||
<Grid ColumnDefinitions="*,8,*">
|
||||
<Button Grid.Column="0" Classes="primary" Height="44" Content="MOVE"
|
||||
Command="{Binding ConfirmMoveHostCommand}" IsEnabled="{Binding !IsBusy}" />
|
||||
<Button Grid.Column="2" Classes="secondary" Height="44" Content="CANCEL"
|
||||
Command="{Binding CancelMoveHostCommand}" />
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user