Public Access
Move the shelf as well as what is on it, and ask what a deletion takes
Two things about a group, and they turn out to be the same argument twice. **A group can be moved to another vault, and it takes everything on it.** The host's move shipped last week and stopped one level too low: moving twenty machines into a shared vault meant twenty trips through a menu, and each one arrived stripped of the group it had been filed under, so the shelf had to be rebuilt by hand on the other side. Moving the shelf is what people were attempting. MOVE sits between EDIT and DELETE over the group cards, and on the card's own menu above the separator DELETE is below — the same place, and the same reasoning, as the host pane's ⋯ entry. **The whole subtree goes, and taking less was never coherent.** A group's children are items of the vault it is leaving, so a parent moved alone leaves them naming a tombstone and they surface as roots in the vault the user has just emptied: half a shelf here and half there, from one gesture that said "move this". The hosts are the same argument and are the half the request was about. The groups go first, top down, and the hosts last. Each item is re-sealed under the destination's key and takes a new id — VaultItemRepository.MoveAsync, which HostGroupRepository now exposes — so nothing pointing at a group can be written until that group has landed and its new id is known, and a child's parent must already be over there. What an interruption leaves is therefore hosts still in the vault they started in, under UNGROUPED: visible, and re-movable. The reverse order would leave hosts in the destination filed under nothing. The parent stays behind and the tags are dropped, which is the host move's rule one level up: both are items of the vault being left, so a reference carried across would resolve on the machine that moved it and dangle for everybody else in the destination. The moved group arrives at the top level, and the panel says so before the press rather than the status line saying it after. Keys and passwords are kept — those genuinely resolve across vaults, and clearing them would take a working host and make one that cannot connect — and any now outside the destination is named, because that is precisely what the other members of it will not be able to resolve. Refused as a whole where anything under the group was written by a newer client, rather than skipped item by item: a move that left behind what it could not re-encode would file some of the shelf in one vault and the rest in the other, which is the state this exists to prevent. Refused with a host editor open, as the drop gesture is, because it rewrites hosts. And the walk carries a visited set, for the reason every walk over this tree does: a group that is its own parent — which two offline clients can build and no editor was ever shown — would otherwise be appended to the move list for as long as there was memory. **Deleting a group now asks what should become of the hosts under it, and that reverses a decision this repository had written down.** The deletion did not touch them: the reference was left dangling, the list resolved it to nothing, and the machines turned up under UNGROUPED. That was right for one of the two things people delete a group for and wrong for the other — a heading being tidied away should leave its machines alone, and a project that has been decommissioned is a shelf and everything on it — and nothing in the code can tell which of the two it is looking at. So it is asked. A tick rather than a pair of options, because the two answers are not equally weighted: keeping the hosts is recoverable and deleting them is not, so the safe answer is the one that needs no decision. It is off on every question, including the one that disarms it, or a tick left standing would destroy the next group's machines on the strength of a decision about the last one's. Once the deletion knows which hosts it means, leaving them naming something that has gone is a state kept for no reason, so the unticked answer writes too: N hosts with the reference cleared, where the ticked one writes N tombstones. That is the N writes HostGroupRepository refuses to hide behind a DeleteAsync overload, made where somebody asked for them and where the count is on screen first. The nested groups take the deleted group's place in the tree rather than being orphaned to the top level. A read-only host is skipped, counted and named, because unfiling it would re-encode a payload this build cannot represent — and the cost of skipping is a dangling id, which every reader here already survives. **Both are the desktop's alone**, and that is not an omission. The phone draws groups as headings in the host list and has never had a way to delete or move one; the two panels take the row of buttons over the group cards, and there is no such row on a 360dp screen to take. One test had to change its premise rather than its assertion. EditingAHostWhoseGroupIsGone built its dangling reference by deleting the group, which now unfiles instead — so it imports a host naming an id nothing resolves, which is what a group deleted on another machine actually looks like and is the only way that state still arises. The picker's placeholder is still needed and still covered. Four places said an item could not be moved between vaults. Two were about a group and were true when written; the other two were left stale by the host's move. All four now say what is true, including the design gaps document, where the chevron beside the vault name stays undrawn for the reason it already had.
This commit is contained in:
@@ -41,6 +41,21 @@
|
||||
Text="{Binding PendingDeletion.Usage}" />
|
||||
</Border>
|
||||
|
||||
<!--
|
||||
The second question, and only a group's deletion has one: whether the machines filed under it go with
|
||||
it. See VaultViewModel.DeleteGroup for why it is asked rather than stated, and DeletionTakesTheHostsToo
|
||||
for why it is a tick rather than a pair of options — the two answers are not equally weighted, and the
|
||||
recoverable one is the one that needs no decision.
|
||||
|
||||
Below the usage box on purpose: that box is the count this is about, and a tick offered above the
|
||||
number it applies to is a tick somebody answers before reading how many.
|
||||
-->
|
||||
<CheckBox IsVisible="{Binding PendingDeletion.HasChoice, FallbackValue=False}"
|
||||
IsChecked="{Binding DeletionTakesTheHostsToo}">
|
||||
<TextBlock Foreground="{StaticResource WarnText}" FontSize="12" TextWrapping="Wrap"
|
||||
Text="{Binding PendingDeletion.Choice}" />
|
||||
</CheckBox>
|
||||
|
||||
<StackPanel Orientation="Horizontal" Spacing="6">
|
||||
<Button Classes="danger" Content="DELETE" Command="{Binding ConfirmDeleteCommand}"
|
||||
IsEnabled="{Binding !IsBusy}" />
|
||||
|
||||
@@ -55,10 +55,11 @@
|
||||
renderer. They are listed in docs/design-import-gaps.md with what ships instead, and none of them is
|
||||
drawn disabled.
|
||||
|
||||
The design's fifth missing control was the vault picker's chevron, and half of it now exists: a host
|
||||
being *created* is asked which vault it goes into, in the editor below. What still does not exist is
|
||||
the other half — moving an existing host — because the two vaults are encrypted under different keys,
|
||||
so that is a delete and a retype rather than an edit.
|
||||
The design's fifth missing control was the vault picker's chevron, and both halves of what it stood for
|
||||
now exist without it: a host being *created* is asked which vault it goes into, in the editor below, and
|
||||
an existing one is moved from the pane's ⋯ menu. The chevron itself stays undrawn, because a chevron on
|
||||
a subtitle implies an edit and this is not one — the two vaults are encrypted under different keys, so a
|
||||
move is a re-seal into one and a tombstone in the other, and the host takes a new id.
|
||||
-->
|
||||
|
||||
<Border Width="304" Background="{StaticResource Sidebar}"
|
||||
@@ -568,8 +569,10 @@
|
||||
<!--
|
||||
◆ WHICH VAULT THIS GROUP WILL LIVE IN, on the same terms as the host editor's picker
|
||||
above: asked while adding, hidden where there is only one vault to write to, and never
|
||||
offered for an existing group, because the vaults are encrypted under different keys and
|
||||
moving an item between them is a delete and a retype.
|
||||
offered for an existing group. Not because the group is stuck — MOVE over the group cards
|
||||
takes it to another vault, with everything on the shelf — but because moving it is a
|
||||
re-seal of every item involved into new ids, which is nothing a SAVE on this form could
|
||||
do, and a picker here would do it as a side effect of correcting a default port.
|
||||
|
||||
A group in a shared vault is what gives a team an arrangement rather than a heap: the
|
||||
people holding that vault's key see the folder, and the hosts inside it inherit its port,
|
||||
|
||||
@@ -231,16 +231,23 @@
|
||||
Foreground="{StaticResource TextDim}" VerticalAlignment="Center" />
|
||||
|
||||
<!--
|
||||
The group's own two actions, beside the group cards rather than in the toolbar, because they
|
||||
act on the card that is selected — and this is where the selection is made. Hidden rather
|
||||
than disabled while DELETE's question is up, as every other pair in this application is, so
|
||||
it cannot be pressed twice, and hidden again when there is nothing for them to act on. What
|
||||
that is, with no card selected, is the group the trail above ends with; see
|
||||
The group's own actions, beside the group cards rather than in the toolbar, because they act
|
||||
on the card that is selected — and this is where the selection is made. Hidden rather than
|
||||
disabled while either panel below is up, as every other row of buttons in this application
|
||||
is, so none of them can be pressed twice, and hidden again when there is nothing for them to
|
||||
act on. What that is, with no card selected, is the group the trail above ends with; see
|
||||
VaultViewModel.GroupTarget.
|
||||
|
||||
MOVE is between them rather than beside DELETE, and it stays visible with nowhere to go: the
|
||||
command answers with a sentence naming the reason, which is more use than a button that has
|
||||
quietly gone. The host pane's ⋯ menu keeps its own entry on the same reasoning.
|
||||
-->
|
||||
<StackPanel Grid.Column="2" Orientation="Horizontal" Spacing="6">
|
||||
<Button Classes="ghost" Content="EDIT" Command="{Binding EditGroupCommand}"
|
||||
IsVisible="{Binding ShowsGroupActions}" />
|
||||
<Button Classes="ghost" Content="MOVE" Command="{Binding MoveGroupCommand}"
|
||||
IsVisible="{Binding ShowsGroupActions}"
|
||||
ToolTip.Tip="Moves this group to another vault, with the groups inside it and every host filed under them." />
|
||||
<Button Classes="ghost" Content="DELETE" Command="{Binding DeleteGroupCommand}"
|
||||
IsVisible="{Binding ShowsGroupActions}" />
|
||||
</StackPanel>
|
||||
@@ -251,6 +258,42 @@
|
||||
<views:ConfirmDeleteCard />
|
||||
</Border>
|
||||
|
||||
<!--
|
||||
◆ MOVING THE GROUP TO ANOTHER VAULT. The host drawer's move panel, one level up and in the
|
||||
place this section's buttons 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 — so it is not
|
||||
drawn in the danger colours the deletion question above it uses.
|
||||
|
||||
The sentence is what the panel is for. A group cannot go anywhere alone: the machines under it
|
||||
are items of the vault it is leaving, and so are the groups nested inside it, so all of them
|
||||
are re-sealed under the destination's key and all of them take new ids. What cannot come is
|
||||
the group it is nested under, which is why it arrives at the top level. Saying that here
|
||||
rather than only in the status line afterwards is the difference between a warning and a
|
||||
surprise.
|
||||
-->
|
||||
<Border Padding="10" Background="{StaticResource Panel}" CornerRadius="6"
|
||||
BorderBrush="{StaticResource Border}" BorderThickness="1"
|
||||
IsVisible="{Binding IsMovingGroup}">
|
||||
<StackPanel Spacing="8">
|
||||
<TextBlock Classes="label" Text="MOVE GROUP TO VAULT" />
|
||||
<ComboBox HorizontalAlignment="Stretch" ItemsSource="{Binding MoveGroupVaultChoices}"
|
||||
SelectedItem="{Binding SelectedMoveGroupVault}">
|
||||
<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 group goes with the groups inside it and every host filed under them, all re-encrypted with the other vault's key. Any group it is nested under stays behind, so it arrives at the top level, and the hosts' tags stay behind with it." />
|
||||
<StackPanel Orientation="Horizontal" Spacing="6">
|
||||
<Button Classes="accent" Content="MOVE" Command="{Binding ConfirmMoveGroupCommand}"
|
||||
IsEnabled="{Binding !IsBusy}" />
|
||||
<Button Classes="ghost" Content="CANCEL" Command="{Binding CancelMoveGroupCommand}" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<!--
|
||||
A ListBox rather than an ItemsControl of buttons, and that is what marks the chosen group for
|
||||
free: selection is a state the control already has, and the tile style draws it the same way
|
||||
@@ -302,14 +345,20 @@
|
||||
act on the card that was right-clicked.
|
||||
|
||||
Only Open takes a parameter, because OpenGroupCommand's null means ALL HOSTS rather than
|
||||
nothing; Edit and Delete read GroupTarget, which the selection the code-behind has just made
|
||||
nothing; the other three read GroupTarget, which the selection the code-behind has just made
|
||||
is the first half of.
|
||||
|
||||
Moving is above the separator that Delete sits below, exactly as it is in the host pane's
|
||||
menu, and for the same reason: it is not a field of the group and it is not a deletion
|
||||
either. The two vaults are encrypted under different keys, so it is a re-seal of the whole
|
||||
shelf into one and a tombstone per item in the other.
|
||||
-->
|
||||
<ListBox.ContextMenu>
|
||||
<ContextMenu>
|
||||
<MenuItem Header="Open" Command="{Binding OpenGroupCommand}"
|
||||
CommandParameter="{Binding SelectedGroup}" />
|
||||
<MenuItem Header="Edit…" Command="{Binding EditGroupCommand}" />
|
||||
<MenuItem Header="Move to another vault…" Command="{Binding MoveGroupCommand}" />
|
||||
<Separator />
|
||||
<MenuItem Header="Delete…" Command="{Binding DeleteGroupCommand}" />
|
||||
</ContextMenu>
|
||||
|
||||
Reference in New Issue
Block a user