diff --git a/README.md b/README.md index 12ca001..69d2399 100644 --- a/README.md +++ b/README.md @@ -352,6 +352,25 @@ vault it is leaving. A picker inside the form would do all of that as a side eff What a move cannot do is reach a machine that has already synced the host, which is the same limit everything else about revocation has. Keys, passwords and buckets take theirs from a standing "new items go to" picker on the Keychain screen and cannot be moved yet. + +**A group can be moved too, and it takes its contents with it** — "Move to another vault…" on the group +card's right-click menu, beside Open, Edit and Delete, which is the whole of what can be done to a group on +the desktop. That is the desktop only, because the phone draws groups as headings in the host list and has +never had a way to delete or move one. It is the same re-seal +and tombstone underneath, applied to every item involved: the group, the groups nested inside it, and every +host filed under any of them, each taking a new id in the destination. Moving less than that was never +coherent — the machines and the child groups are items of the vault the group is leaving, so a group that +travelled alone would leave half a shelf behind. What stays is the group it was itself nested under, which +belongs to the old vault, so it arrives at the top level; the hosts' tags stay for the same reason. Keys and +passwords are kept, because those genuinely resolve across vaults, and the sentence afterwards names any +that are now outside the destination. + +**Deleting a group asks what should become of the hosts under it.** The default answer keeps them: the +reference is cleared and they move to UNGROUPED. Ticking the box deletes them with it. Both answers are a +change — the deletion used to leave the hosts holding an id that no longer resolved, which looked the same +and cost nothing, and stopped being the right shape once the deletion could take them with it. A group with +nothing filed under it is not asked. Whichever answer is given, the groups nested inside take the deleted +group's place in the tree rather than being orphaned to the top level. Both default to your personal vault and neither moves on its own, because an item put in a shared vault is visible to everybody holding that vault's key. Choosing a vault in the host editor also decides which groups it can be filed under: a group is an item like any other and lives in exactly one vault. @@ -634,6 +653,16 @@ keychain plus a terminal — and the spike that gates all of it. than to a shell that never opens. Given a walk that had to be cycle-safe anyway, refusing to nest bought nothing. + Membership living on the host has one further consequence, and it took two goes to settle. Deleting a + group could not clear it without rewriting every host under the heading, so at first it did not: the hosts + kept an id that resolved to nothing and turned up under UNGROUPED, which reads identically and costs no + writes. That held until the deletion had to be able to take the hosts *with* it — a group is sometimes a + heading being tidied away and sometimes a project that has been decommissioned, and nothing in the code can + tell which. Once a deletion knows which hosts it means, leaving them naming something that has gone is a + state kept for no reason, so both answers now write: N deletions, or N hosts with the reference cleared. + The dangling case still has to be survived everywhere it is read, because a group deleted on *another* + machine arrives exactly that way. + Inserting a snippet types it at the prompt and stops. Pressing Enter is a per-snippet decision, off by default, and the reason is worth stating: a terminal is one input stream with no notion of being at a prompt — the remote may be in an editor, or at a password prompt with the echo off — so this client cannot diff --git a/docs/design-import-gaps.md b/docs/design-import-gaps.md index 027f97d..131b6c3 100644 --- a/docs/design-import-gaps.md +++ b/docs/design-import-gaps.md @@ -157,7 +157,7 @@ the chrome, hosts and terminals, file transfer, the vault, teams, and preference > | **Add Telnet**, and **Serial** in the toolbar | Omitted. `ISshConnection` is the only transport there is. This is also why the card subtitle's `ssh` is a constant today rather than a reading — it is stated in `HostRowViewModel.Summary`, which is the one place in this interface where a constant is printed on purpose. | > | **+ SSH ID, Certificate, FIDO2** | Omitted. `IDENTITIES` and `CERTIFICATES` have been on this document's list since the first import — neither is even a reserved `SyncEntityType` — and there is no security-key path anywhere in the SSH layer. One control offering three item types that do not exist. | > | The **Backspace / Default** row | Omitted. It is a terminal setting, and the client has no preferences store and no frame to carry one to the renderer — see the Preferences section. It would be a control whose value could not survive the window closing. | -> | The **chevron beside the vault name** | The name alone, and the move behind the pane's ⋯ menu instead. A host *can* now be moved between vaults, so the gap is no longer that there is nothing to offer — it is that a chevron on a subtitle implies an edit, and this is not one: the two vaults are encrypted under different keys, so it is a re-seal into one and a tombstone in the other, the host takes a new id, and its group and tags stay behind. A control that implied "just change this field" would be describing something else. Where a *new* host goes is still asked in the host editor, as a picker beside the name; keys, passwords and buckets take theirs from the keychain screen's standing picker and cannot be moved yet. | +> | The **chevron beside the vault name** | The name alone, and the move behind the pane's ⋯ menu instead. A host *can* now be moved between vaults, so the gap is no longer that there is nothing to offer — it is that a chevron on a subtitle implies an edit, and this is not one: the two vaults are encrypted under different keys, so it is a re-seal into one and a tombstone in the other, the host takes a new id, and its group and tags stay behind. A control that implied "just change this field" would be describing something else. Where a *new* host goes is still asked in the host editor, as a picker beside the name. A group moves too, from its card's right-click menu, and takes its nested groups and every host filed under them; keys, passwords and buckets take theirs from the keychain screen's standing picker and cannot be moved yet. | > | **Show more ⌄** | Not drawn as a disclosure. What it would hide — notes, the relay switch, forgetting the host key — is in the editor, one press away, and a second fold inside a pane that already scrolls is a second place for a field to be missing from. | > | **Port Forwarding** in the sidebar | Nothing, for the third time in this document. | > | The host grid's toolbar avatar, share and tag-filter controls | Omitted, as in v3 and for the same reasons. | diff --git a/docs/manual-checks.md b/docs/manual-checks.md index cb2d819..b9cef04 100644 --- a/docs/manual-checks.md +++ b/docs/manual-checks.md @@ -371,13 +371,35 @@ takes only the group that is open. ### 3.3 Deleting a group with hosts in it -Right-click a group with hosts in it and choose **Delete…**. +Right-click a group with hosts in it and choose **Delete…**. Do it twice: once leaving the tick alone, and +once — on another group — ticking it. -**Pass:** the question names how many hosts are filed under it and says they stay. Agreeing removes the -group; the hosts lose their chip and are otherwise unchanged. +**Pass:** the question names how many hosts are filed under it, says they stay and move to UNGROUPED, and +offers a tick that would delete them as well. The tick starts clear, and it starts clear again on the next +group even if it was set on the last one. Left clear, agreeing removes the group and the hosts stay, without +a chip and otherwise unchanged. Ticked, the hosts go with it — and only the hosts that were filed under that +group. A group with nothing under it is asked no second question and shows no tick. -**Failure means:** if the hosts vanish, the delete is rewriting host payloads, which it must not — see -`HostGroupRepository`. +**Failure means:** a tick that carries from one question to the next is the reset in `OnPendingDeletionChanged` +having gone, and it deletes machines on the strength of a decision about a different group. Hosts that keep +the chip after an unticked delete are the unfiling not happening: they still name a group that is gone, which +is what this used to do on purpose and no longer should. + +### 3.3a Moving a group to another vault · **needs a second vault** + +Build `outer › inner` with a host in `inner`, all in your personal vault, then right-click **outer** and +choose **Move to another vault…**. Pick the shared vault and press MOVE. + +**Pass:** the panel says what travels and what does not before you press anything. Afterwards all three items +carry the destination's badge, `inner` is still inside `outer` and the host is still inside `inner` — every +one of them under an id it did not have a moment ago. The sentence names the vault, the counts, and the fact +that the group now sits at the top level if it was nested. Nothing is left behind in the vault it came from. + +**Failure means:** a host under UNGROUPED in the destination is the group id having been carried across +rather than remapped — the ids are the destination's making, so every reference has to be rewritten as its +target lands. Anything still in the source vault is a partial move, which is survivable by design but should +not happen with the network up: the groups are written top-down and the hosts last, so an interruption leaves +hosts behind and never a shelf with nothing on it. ### 3.4 A group deleted on another machine · **needs two machines** diff --git a/src/DodoSSH.Client.App/Views/ConfirmDeleteCard.axaml b/src/DodoSSH.Client.App/Views/ConfirmDeleteCard.axaml index 0d08387..93b1e01 100644 --- a/src/DodoSSH.Client.App/Views/ConfirmDeleteCard.axaml +++ b/src/DodoSSH.Client.App/Views/ConfirmDeleteCard.axaml @@ -41,6 +41,21 @@ Text="{Binding PendingDeletion.Usage}" /> + + + + +