Merge branch 'claude/groups-vault-sharing-e4b154'
ci / android head (push) Canceled after 0s
ci / api image (push) Canceled after 0s
ci / build and test (push) Canceled after 2s

# Conflicts:
#	src/DodoSSH.Client.Shell/ViewModels/VaultViewModel.cs
This commit is contained in:
2026-08-04 16:28:29 +02:00
8 changed files with 765 additions and 112 deletions
+1 -1
View File
@@ -307,7 +307,7 @@ caption buttons and window title drawn on top of the application's own — two s
| Tag chips (`nginx`, `eu`, `pg16`) | client-domain | A tag item type, and a way to put one on a host. | **Shipped.** `Tag = 5` is a live item kind and `HostSecret.TagIds` names them, merged per tag so two people tagging one host both keep theirs — which is what `HostTag = 6` was going to buy, so it stays reserved and unused. Chips are drawn on host rows on both heads and toggled in the host editor, which also creates one inline; renaming and deleting are a TAGS category on the keychain screen. The filter box still searches name, address and notes only — a chip is read rather than typed. |
| Groups `PRODUCTION` / `STAGING` / `PERSONAL` | client-domain | A host-group item type (`HostGroup = 4`, reserved) or a group field on `HostSecret`. | **Shipped**, as both: `VaultHostGroup` is a synced item kind and `HostSecret.GroupId` names one. A group carries a `ParentId` and the defaults its hosts inherit, and the two heads draw the nesting differently on purpose. The desktop is a grid of cards holding one level at a time, the way a directory pane holds one directory; the phone has no room for a row of cards, so it flattens the whole tree into one heading per group in label order with no indentation. A keychain with no groups renders exactly as it did before on both — one flat list, no headings and no cards. |
| Group badge `TEAM·PLATFORM` | server | **Built in M3.** | The vault's name on each row, and the personal vault ordered first. Not the team's name: two of a team's vaults would then carry the same badge and the badge would be naming the wrong thing. Distinct from the groups above, and deliberately so — a group is a shelf the user chose, a vault is who can read the item. |
| Groups on a **team's** hosts | client-domain | A vault id on each group row for rename and delete, and a way to tell two vaults' identically-named groups apart in a list with one heading per group. | **Half shipped, and the half that shipped had to.** Every readable vault's groups are now read into the resolution map, because a group lends a port, a username and a binding — so a host whose group went unread would silently dial 22 as nobody while the machine is on 2222 as `deploy`. A missing heading is cosmetic; a missing port is a connection to the wrong place. The editable list is still the active vault's alone, so a group a teammate made has no card and no heading and cannot be renamed from here. A host filed into one is drawn at the outermost level of the desktop's grid and under UNGROUPED on the phone — reachable either way, which is the point — with the chip on its card still naming the group, because the label is resolved through the wide map while the level is decided from the narrow list. |
| Groups on a **team's** hosts | client-domain | A vault id on each group row for rename and delete, and a way to tell two vaults' identically-named groups apart in a list with one heading per group. | **Shipped, and it is what makes a shared vault an arrangement rather than a heap.** Both halves are paid for: the row carries the vault it came out of, so rename and delete go back to it, and the vault's name is drawn beside the group's on the desktop's cards and the phone's headings wherever the session holds more than one — which is what lets two `production` groups sit side by side. The group editor asks which vault a new group goes into, on the terms the host editor's picker set: while adding only, hidden at one writable vault, and never offered afterwards, because the two are encrypted under different keys. The parent picker is that vault's alone, for the reason the host editor's group picker is — a parent in another vault is a level half the key holders cannot resolve. Dragging a host card onto a group card in another vault is refused with the reason, rather than filing it under an id nobody in its own vault can read. The resolution map stays wider than the list and still spans hidden vaults: a group lends a port, a username and a binding, so a host whose group went unread would silently dial 22 as nobody while the machine is on 2222 as `deploy`. |
| Per-host status dot, three colours | client-ssh | The amber state would mean "reachable but not connected", and nothing here ever probes a host. | Two states, both real: green when a terminal is open on that host, grey when not. |
| `· ⤷ bastion-eu` in the host subtitle | client-ssh | **Jump hosts are data-only.** `HostSecret.JumpHostIds` is a `JumpChain` that is stored, encrypted, synced and three-way merged — and nothing reads it at connect time. `SshConnectionRequest` carries one host. | Omitted. The stored chain is preserved untouched by every edit. |
| `SPLIT ⌘D` and side-by-side panes | client-ssh + ui | The renderer stacks panes and shows one (`terminal.css`: `.pane { position:absolute; inset:0; display:none }`). Tiling needs a real pane geometry and a splitter. | Omitted. Tabs ship instead, over the same one-WebView multiplexing. |
+25
View File
@@ -1289,6 +1289,31 @@ rather than a broken role.
under the people who share it is an administrative act reached without the role for it. The server refuses
it too — this is the interface not offering what the server would turn down.
### 12.10 A group made in a shared vault arrives as a group, not as a heap · **needs two accounts**
1. As Alice, on HOSTS, press + NEW GROUP, choose the shared vault in the editor's VAULT picker, name it
`production`, and give it a default port and username.
2. Add two hosts to the same shared vault and file them under it.
3. Make a second group called `production` in the **personal** vault.
4. Sync, then look at Bob's machine after his own sync.
**Pass on Alice's:** the two cards are told apart by the vault name printed under each — same name, two
folders — and on the phone the two headings carry the same badge. Opening either shows only its own hosts.
Dragging one of the shared vault's host cards onto the personal `production` card is **refused with a
sentence naming both vaults**, and the host stays where it was.
**Pass on Bob's:** the group is a card and a heading on his machine too, with the hosts inside it, and the
port and username they dial are the ones Alice typed into the group rather than 22 and his own account. He
can rename it, and the rename comes back to Alice rather than arriving as a second group in his personal
vault.
**Failure means:** a group that reaches Bob as UNGROUPED hosts is the resolution map having gone narrow
again — cosmetic on its own, except that the port and the username go with it, so his terminal dials the
wrong place. A rename of his that turns up as a new group in his own vault is the editor writing to the
active vault rather than to the row's, which forks the shelf and leaves Alice's untouched. Two identical
cards with no vault under them means one of them is a folder somebody outside the team can read, and
nothing on screen says which.
---
## Phase 13 — Unlocking the phone with a fingerprint