Public Access
Let a snippet be shared to a vault, the way a host already can
A snippet was a first-class vault item everywhere except where it mattered: the crypto, the sync, the server table and every registry already treated it exactly as they treat a host, and the screen read it out of the active vault alone. So the one command a team most obviously wants to hold in common — the incantation somebody worked out once and everybody else retypes — was the only item kind that could not leave the machine that wrote it. The read is the half that had to come first, and it is why this is not simply a MoveAsync. ReloadSnippetsAsync now lists every readable vault rather than the active one, in the shape ReloadHostsAsync and ReloadKeysAsync already use: the vault new items go into first, then by vault name, then by label, with a badge on the row only where there is more than one vault to tell apart. Without that, a snippet moved into a team vault would have disappeared from the very screen that moved it, and one a colleague wrote there would never have arrived at all — sharing would have looked like losing. Three writes were pinned to the active vault and each one broke differently once the list spanned several. The delete tombstoned in the wrong vault, which tombstones nothing and leaves the snippet on screen. The save is the bad one: an update sent to the active vault creates a second snippet there and leaves the team original untouched, so the person editing sees their fix and nobody else ever does. That is a fork with no symptom, which is why the vault is now a parameter and the screen latches it when the editor opens — the chosen vault for a new snippet, the row own vault for an existing one — rather than reading it back off a selection that can move under a half-typed form. VaultViewModel has carried editingHostVaultId for the same reason since hosts crossed vaults. Two controls rather than one, and that is the same line the host pane draws. The editor asks which vault a new snippet is filed into; MOVE re-seals an existing one under another key and tombstones the first. Putting the second inside the first would let somebody correcting a typo hand a command to a team by leaving a picker where they found it, so the picker is not drawn for an existing snippet at all. Both live on SnippetsViewModel rather than VaultViewModel because this screen owns its editor, unlike the host drawer; the writing they ask for is still the vault. A snippet crosses whole, which is the one way this is simpler than the host it copies. A host leaves its group and its tags behind because both are items of the vault it came from and would dangle for everybody in the destination. A snippet is a label, a command and a note, and none of them points at anything — so there is nothing to strip, nothing to report as left behind, and what the copy says instead is the thing that is actually at stake: who can read the command afterwards. For a command that may carry a hostname or a path, that is the whole decision. Two judgement calls worth finding later. A hidden vault now hides its snippets, filtered in the screen projection rather than in VaultViewModel.Snippets, which is the rule keys and passwords already follow: the list stays whole so nothing that resolves against it breaks, and the projection is what a preference about reading gets to change. And the nav rail count is left spanning vaults unfiltered, because Vault.Hosts.Count beside it is unfiltered too — filtering one of the four would make the rail disagree with itself. Four flow tests in VaultSharingTests, beside the host ones they mirror: the move re-seals with a new id and carries the runs-on-insert flag across, the move with nowhere to go refuses rather than opening an empty picker, the editor files into the vault chosen on it, and the edit of a shared snippet goes back to its own vault instead of forking. That last one is the regression the latch exists for and the only one whose absence has no visible symptom. Plus a layout test with the move panel open, since that paragraph wraps in a 300-pixel column and the desktop pane it lands in is measured. The whole suite passes: 1660 tests, none failing.
This commit is contained in:
@@ -63,6 +63,14 @@
|
||||
IsVisible="{Binding Badge, Converter={x:Static StringConverters.IsNotNullOrEmpty}}">
|
||||
<TextBlock Text="{Binding Badge}" FontSize="9.5" />
|
||||
</Border>
|
||||
<!--
|
||||
Which vault this one is in, drawn only where there is more than one to be in. A shared
|
||||
snippet is a command the rest of a team can read and insert; the row is where somebody
|
||||
decides whether the thing they are about to edit is theirs alone.
|
||||
-->
|
||||
<Border Classes="chip" Padding="4,0" IsVisible="{Binding HasVaultBadge}">
|
||||
<TextBlock Text="{Binding VaultBadge}" FontSize="9.5" />
|
||||
</Border>
|
||||
</StackPanel>
|
||||
<!--
|
||||
Newlines shown as ⏎ rather than dropped. A three-line snippet flattened into one run of
|
||||
@@ -89,6 +97,16 @@
|
||||
IsEnabled="{Binding HasSelection}" />
|
||||
<Button Classes="ghost" Content="DELETE" Command="{Binding DeleteCommand}"
|
||||
IsEnabled="{Binding HasSelection}" />
|
||||
<!--
|
||||
Sharing a snippet, which is what moving one into a team's vault is. Beside EDIT rather than
|
||||
inside it: the two vaults are encrypted under different keys, so this is a re-seal into one and
|
||||
a tombstone in the other — nothing a SAVE could do — and a picker inside the form would let
|
||||
somebody correcting a typo hand a command to a team by leaving it where they found it. The
|
||||
picker itself opens beside the snippet, in the pane on the right.
|
||||
-->
|
||||
<Button Classes="ghost" Content="MOVE TO VAULT…" Command="{Binding MoveCommand}"
|
||||
IsEnabled="{Binding CanMove}"
|
||||
ToolTip.Tip="Re-encrypts this snippet with another vault's key. Everybody who holds that key can then read and insert it." />
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
@@ -102,6 +120,27 @@
|
||||
<!-- ============ The editor ============ -->
|
||||
<StackPanel Spacing="6" IsVisible="{Binding IsEditing}">
|
||||
<TextBox Text="{Binding EditorLabel}" PlaceholderText="name" />
|
||||
|
||||
<!--
|
||||
Which vault a *new* snippet is filed into, asked on the form it is being typed into rather
|
||||
than through a standing preference elsewhere. Not drawn for an existing snippet — its vault is
|
||||
not a field of this form, and changing it is MOVE — and not drawn at all where there is only
|
||||
one vault to choose between, because a control offering one option is a question nobody asked.
|
||||
-->
|
||||
<StackPanel Spacing="4" IsVisible="{Binding ShowsEditorVaultChoice}">
|
||||
<TextBlock Classes="label" Text="VAULT" />
|
||||
<ComboBox HorizontalAlignment="Stretch" ItemsSource="{Binding EditorVaultChoices}"
|
||||
SelectedItem="{Binding EditorSelectedVault}">
|
||||
<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="A shared vault means everybody holding its key can read this command and insert it into their own terminals." />
|
||||
</StackPanel>
|
||||
|
||||
<!--
|
||||
Stored exactly as typed — no trimming, no newline normalisation. A here-document's terminator
|
||||
has to arrive on a line of its own, and tidying the trailing newline off it leaves the shell
|
||||
@@ -133,6 +172,16 @@
|
||||
<TextBlock Classes="mono" Text="{Binding Selected.Label}" FontSize="13"
|
||||
FontWeight="SemiBold" Foreground="{StaticResource Text}" TextWrapping="Wrap" />
|
||||
|
||||
<!--
|
||||
Named here as well as on the row, because this pane is where somebody decides to put a
|
||||
command into a production terminal, and who else holds a key to it is part of that decision.
|
||||
The row's badge is off the screen by the time this is being read.
|
||||
-->
|
||||
<Border Classes="chip" Padding="4,0" HorizontalAlignment="Left"
|
||||
IsVisible="{Binding HasSelectionVaultBadge}">
|
||||
<TextBlock Text="{Binding SelectionVaultBadge}" FontSize="9.5" />
|
||||
</Border>
|
||||
|
||||
<TextBlock Classes="label" Text="COMMAND" Margin="0,10,0,4" />
|
||||
<Border Background="{StaticResource Raised}" BorderBrush="{StaticResource Border}"
|
||||
BorderThickness="1" CornerRadius="4" Padding="8">
|
||||
@@ -150,18 +199,51 @@
|
||||
above it is — so "INSERT" alone would leave somebody working out which of six open tabs is
|
||||
about to receive a command, at the moment that is worst to be wrong about.
|
||||
-->
|
||||
<Button Classes="accent" Content="{Binding InsertLabel}" Margin="0,14,0,0"
|
||||
HorizontalAlignment="Left"
|
||||
Command="{Binding InsertCommand}" IsEnabled="{Binding CanInsert}"
|
||||
ToolTip.Tip="Types the command at the prompt and stops. Nothing runs until you press Enter there." />
|
||||
<StackPanel Spacing="6" IsVisible="{Binding ShowsSelectionActions}">
|
||||
<Button Classes="accent" Content="{Binding InsertLabel}" Margin="0,14,0,0"
|
||||
HorizontalAlignment="Left"
|
||||
Command="{Binding InsertCommand}" IsEnabled="{Binding CanInsert}"
|
||||
ToolTip.Tip="Types the command at the prompt and stops. Nothing runs until you press Enter there." />
|
||||
|
||||
<Button Classes="danger" Content="{Binding RunLabel}" HorizontalAlignment="Left"
|
||||
Command="{Binding RunCommand}"
|
||||
IsVisible="{Binding SelectionRuns}" IsEnabled="{Binding CanInsert}"
|
||||
ToolTip.Tip="Types the command and presses Enter. Offered because this snippet is marked as one that runs." />
|
||||
<Button Classes="danger" Content="{Binding RunLabel}" HorizontalAlignment="Left"
|
||||
Command="{Binding RunCommand}"
|
||||
IsVisible="{Binding SelectionRuns}" IsEnabled="{Binding CanInsert}"
|
||||
ToolTip.Tip="Types the command and presses Enter. Offered because this snippet is marked as one that runs." />
|
||||
|
||||
<TextBlock Classes="hint" FontSize="11" TextWrapping="Wrap" Margin="0,10,0,0"
|
||||
Text="Whatever is in the terminal receives this. Nothing here can tell whether that is a shell prompt, an editor, or a password prompt with the echo off — so check the tab before you insert." />
|
||||
<TextBlock Classes="hint" FontSize="11" TextWrapping="Wrap" Margin="0,10,0,0"
|
||||
Text="Whatever is in the terminal receives this. Nothing here can tell whether that is a shell prompt, an editor, or a password prompt with the echo off — so check the tab before you insert." />
|
||||
</StackPanel>
|
||||
|
||||
<!--
|
||||
◆ SHARING THE SNIPPET, which is what moving it into a team's vault amounts to. A picker and
|
||||
two buttons, not a question with a yes: what is being asked is which vault, and a move is
|
||||
undone by moving it back rather than by being careful — so this is not drawn in the danger
|
||||
colours the deletion question uses.
|
||||
|
||||
It takes the insert controls' place while it is up, for the reason the host pane hides
|
||||
CONNECT: the button that opened this is still on screen otherwise, offering to open it again.
|
||||
|
||||
The sentence is the part worth keeping. Unlike a host, a snippet crosses whole — it has no
|
||||
group and no tags to leave behind — so what there is to say is who can read it afterwards,
|
||||
and for a command that may carry a hostname or a path that is the whole of the decision.
|
||||
-->
|
||||
<StackPanel Spacing="8" Margin="0,14,0,0" IsVisible="{Binding IsMoving}">
|
||||
<TextBlock Classes="label" Text="MOVE TO VAULT" />
|
||||
<ComboBox HorizontalAlignment="Stretch" ItemsSource="{Binding MoveVaultChoices}"
|
||||
SelectedItem="{Binding SelectedMoveVault}">
|
||||
<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="The snippet is re-encrypted with the other vault's key, so everybody who holds that key can read this command and insert it — and nobody else can. Nothing else about it changes." />
|
||||
<StackPanel Orientation="Horizontal" Spacing="6">
|
||||
<Button Classes="accent" Content="MOVE" Command="{Binding ConfirmMoveCommand}" />
|
||||
<Button Classes="ghost" Content="CANCEL" Command="{Binding CancelMoveCommand}" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user