diff --git a/README.md b/README.md
index 7bb53b7..112b81e 100644
--- a/README.md
+++ b/README.md
@@ -148,8 +148,9 @@ dotnet run --project src/DodoSSH.Client.App
In the app, enter `http://localhost:5233` as the server. Your browser opens for sign-in — the realm ships
`alice` / `alice` — then choose a vault passphrase and **write down the recovery code**, which cannot be
skipped and cannot be recovered from the server. You can then add a host and open a shell on it — double-click
-it in the sidebar, or select it and press **CONNECT**, which is the same command with the password box beside
-it. Keycloak's admin console is at `http://localhost:18080` (`admin` / `admin`).
+its card, or select it and press **CONNECT** in the drawer that opens beside the grid, which is the same
+command with the password box above it. Keycloak's admin console is at `http://localhost:18080`
+(`admin` / `admin`).
You can also add an SSH key, which is stored in the vault like a host and synced the same way: paste the
private key, then edit a host and pick that key from its **key** dropdown. From then on that host
@@ -270,13 +271,20 @@ fit 360dp.
Its interface is the **v2 design**: four destinations in a bottom bar — Hosts, Terminal, Keychain and More
— with snippets, SFTP, S3 buckets, logs and preferences one tap deeper behind the last. Both heads are on
-that design now; the desktop's own v2 is a 190-pixel labelled sidebar in place of the icon rail, a centred
+that design now; the desktop's own v2 is a 190-pixel labelled nav rail in place of the icon rail, a centred
search box in the titlebar, and session tabs as pills. Its light theme is not built — see
[`docs/design-import-gaps.md`](docs/design-import-gaps.md) — so the application is dark on both.
-Widening the sidebar moved the desktop window's minimum from `880x560` to `1016x574`, which leaves every
+Widening the rail moved the desktop window's minimum from `880x560` to `1016x574`, which leaves every
screen exactly the width it was designed against.
+A third desktop pass has since moved the furniture. The tab strip belongs to the window rather than to the
+terminal: **Vaults**, **SFTP** and **S3** are fixed tabs at its head and open terminals follow them, which
+took SFTP and S3 out of the nav rail — they are the two destinations you stay in while something runs. The
+hosts screen became a grid of cards, groups above and hosts below, with a right-hand drawer for whichever
+host is selected and for both editors; the 268-pixel host sidebar is gone. Text is white rather than the
+design's blue-tinted `#E3E7F4`, and the type scale is a point larger.
+
File transfer **is** here now, in the shape scoped storage allows: one remote pane and the queue, over
either an SFTP host or a bucket. There is no local pane, because there is no browsable local filesystem to
put in one — moving a file *in* from the phone needs the system document picker and is the next piece of
diff --git a/docs/design-import-gaps.md b/docs/design-import-gaps.md
index 336c364..078e601 100644
--- a/docs/design-import-gaps.md
+++ b/docs/design-import-gaps.md
@@ -68,7 +68,38 @@ the chrome, hosts and terminals, file transfer, the vault, teams, and preference
> | **Split ⌘D** | Still omitted — the renderer stacks panes and shows one; tiling needs a pane geometry it has not got. |
> | macOS traffic lights, and `⌘K` | The window's own minimise/maximise/close, and `CTRL K`. Development is Windows-first and the chrome is `BorderOnly` for a documented reason. |
> | No status bar | Kept, and cut down to the one thing the titlebar does not now carry: `Vault.Status`, which is the only channel this application has for saying a save failed or a merge picked a winner. The design is a mock-up of a working afternoon and has nowhere to put a sentence like that. |
-> | The sidebar's five destinations, and a **Team vault** card at its foot | Nine destinations, because Pins, Teams, Import and Preferences are built screens and dropping their entry would strand them. The card is not drawn: it is a second route to a screen already in the list, carrying a seat count nothing here produces. |
+> | The sidebar's five destinations, and a **Team vault** card at its foot | Seven destinations, because Pins, Teams and Preferences are built screens and dropping their entry would strand them — and two fewer than v2 shipped with, because SFTP and S3 became tabs; see v3 below. The card is not drawn: it is a second route to a screen already in the list, carrying a seat count nothing here produces. |
+>
+> ## The desktop's v3
+>
+> A third pass, and the smallest of the three: it moves furniture rather than adding screens. Nothing in it
+> needed a layer below `client-app`, which is why it has no table of its own — there was nothing to omit.
+>
+> **The tab strip became the window's, not the terminal's.** Three fixed tabs sit at its head — Vaults,
+> SFTP, S3 — and terminal tabs follow them. SFTP and S3 left the nav rail to get there, which is the one
+> semantic change: they are the two destinations you *stay in* while something runs, and a rail entry is
+> for somewhere you go and come back from. The rail is drawn under Vaults alone, so SFTP, S3 and a terminal
+> each get the full window width instead of `826`. See `MainWindowViewModel.IsVaultsTab` for why the tab is
+> a page test rather than a fourth `ShellSurface`.
+>
+> **The hosts screen became a grid of cards** — groups above, hosts below — and the 268-pixel host sidebar
+> went with it. That column was choosing among forty machines *and* editing one of them at two-thirds
+> width; the grid took the first job at full width and a 304-pixel right-hand drawer took the second. The
+> drawer collapses when nothing is selected, which is most of the time. Pressing a group card narrows the
+> grid to that group; `SHOW ALL` is the way back.
+>
+> **The type scale went up a point and the text ramp went white.** `#E3E7F4` was a blue-tinted white on
+> blue-black surfaces, which costs contrast twice — once for being darker than white and once for sharing
+> a hue with what it is drawn on. Every step of the ramp moved with the top, so the intervals the design
+> chose are kept. Both are in the shared palette, so the phone has them too.
+>
+> | v3 element | What ships instead |
+> | --- | --- |
+> | The hosts toolbar's view-mode switch (grid / list / table) | One view. A switch between three layouts where only one is built is two disabled buttons. |
+> | The hosts toolbar's tag filter, calendar and share control | Omitted. There are no saved filters, nothing in a vault carries a date, and sharing outside a team vault does not exist. Tags are real and are searched by being read off the cards. |
+> | **Serial** beside **Terminal** in the toolbar | Omitted. Every session here is an SSH channel; a serial transport is not a button, it is a second session kind. |
+> | The strip's tabs inside the titlebar row, with a hamburger | A row of its own under the titlebar. The titlebar already carries the drag region, the search box, the sync light and three window controls, and the strip scrolls — putting both on one 44-pixel row would make the thing that scrolls fight the thing that drags. |
+> | A count on the S3 tab | Omitted. The rail entry it replaces carried one; a number on two of five tabs reads as a fact about those two rather than as the tab's own state, and a terminal tab has nothing to count. The count is on the S3 screen. |
Most of it landed. This file is the rest: every element of that design with nothing behind it, which
project each piece would have to land in, and **what the shipped interface does instead**. That last
@@ -219,7 +250,7 @@ caption buttons and window title drawn on top of the application's own — two s
| Pane header `aes256-gcm` | client-ssh | **The closest miss on this list.** `SshNetConnection` holds the `SshClient`, so `ConnectionInfo.CurrentServerEncryption` is right there — it just is not on `ISshConnection` or surfaced by `TerminalWorkspace`. | Omitted; the tab strip shows the account and endpoint actually dialled. |
| Pane header showing the running command and `following` | client-ssh | The host moves opaque bytes and never parses terminal output. Would need shell integration (OSC 133) on the remote. | Omitted. |
| A `local · zsh` tab | client-ssh | Every session here is an SSH channel. Needs ConPTY and a second session kind. | Omitted. |
-| Tab strip `+` button | ui | Not missing so much as redundant: the real operation is *select a host, press Connect*, which the sidebar already is. | Omitted. Connect opens a tab; Ctrl+K opens one by name. |
+| Tab strip `+` button | ui | Not missing so much as redundant: the real operation is *select a host, press Connect*, which the hosts grid already is. | **Shipped**, as the palette rather than a menu: it opens what Ctrl+K opens, so the strip and the shortcut are one way of doing one thing. A `MenuFlyout` offering "SSH" and "local shell" is the nicer answer and is not verifiably safe above the terminal's native child window — and there is no local shell to offer. |
| Terminal font size (`--termfs`, 11–16px) | client-storage | See preferences. | Fixed at the renderer's 13px. |
---
@@ -356,10 +387,13 @@ lists the rest as absent rather than omitting it silently.
## Two things the import changed on purpose
-**Hosts left the vault column.** They have their own sidebar beside the terminal, and the vault screen
-holds keys, passwords and pinned host keys. This follows the design, and it is also the better split: the
-host list is what you look at while you work, and the rest is what you go and manage. `VaultSection` lost
-its `Hosts` member and gained `All`.
+**Hosts left the vault column.** They have their own screen and the vault screen holds keys, passwords and
+pinned host keys. This follows the design, and it is also the better split: the host list is what you look
+at while you work, and the rest is what you go and manage. `VaultSection` lost its `Hosts` member and
+gained `All`.
+
+That screen was a 268-pixel sidebar beside the terminal when this paragraph was written, and v3 made it a
+grid of cards with a drawer — see above. The split it describes did not change; only which half is wide.
**Tabs moved to the shell, not the vault.** Locking disposes the vault and deliberately leaves shells
running, so a tab list rebuilt per unlock would lose track of sessions that are still connected — the very
diff --git a/docs/manual-checks.md b/docs/manual-checks.md
index 0acef2e..8b71440 100644
--- a/docs/manual-checks.md
+++ b/docs/manual-checks.md
@@ -22,10 +22,11 @@ Each item says what to do, what a pass looks like, and what a failure would mean
### 1.1 No screen is sliced at the WebView's left edge · **the important one**
-Open two terminals, then visit every nav rail entry in turn — HOSTS, FILES, KEYS, TEAM, PREFS.
+Open two terminals, then visit every nav rail entry in turn — Hosts, Keychain, Pins, Snippets, Logs, Teams,
+Preferences — and both of the fixed tabs, SFTP and S3.
**Pass:** each screen draws whole, its buttons all clickable, and the tab strip stays across the top of all
-five.
+nine. The nav rail is there for the seven and gone for the two, because it belongs to the Vaults tab.
**Failure means:** a screen is not collapsing while the terminal shows. The terminal is a native child
window and composites above everything Avalonia paints, so the symptom is a screen cut off at the WebView's
@@ -268,8 +269,8 @@ single-process test can reach.
Open the hosts screen without creating any group.
-**Pass:** the sidebar list is the flat list of hosts it always was — no headings, no UNGROUPED, nothing
-saying the hosts are unfiled.
+**Pass:** the grid is the flat wrap of host cards it always was — no GROUPS section above it, no headings
+between the cards, no UNGROUPED, nothing saying the hosts are unfiled.
**Failure means:** the "invisible until used" property is gone, and every existing user gets a heading they
did not ask for. `RebuildSidebarRows` returns early when `Groups` is empty; that early return is the feature.
@@ -281,9 +282,13 @@ Make two groups, file some hosts into each through the host editor, then click a
**Pass:** the heading's chevron flips and its hosts disappear; the count on the heading does not change,
because it counts what is in the group rather than what is on screen. Clicking again brings them back.
-**Also check:** clicking a heading does not change which host is selected — the buttons at the foot of the
-sidebar go on acting on the same machine. This is asserted in a test, but the test drives the view model
-directly; what it cannot see is whether the `ListBox` writes something else back through the binding first.
+**Also check:** clicking a heading does not change which host is selected — the drawer stays open on the
+same machine and its EDIT and DELETE go on acting on it. This is asserted in a test, but the test drives the
+view model directly; what it cannot see is whether the `ListBox` writes something else back through the
+binding first.
+
+**Then press a group card.** The grid narrows to that group's hosts, the card is marked as chosen, and
+SHOW ALL appears beside GROUPS. Pressing it brings the rest back and unmarks the card.
### 3.3 Deleting a group with hosts in it
@@ -639,10 +644,10 @@ back.
### 7.6 Dragging a host into a group · **least covered, like all drag and drop**
-Make two groups and file a host into one. Drag a host row onto another group's heading; onto a host row
+Make two groups and file a host into one. Drag a host card onto another group's heading; onto a host card
inside another group; and onto UNGROUPED.
-**Pass:** the row under the pointer washes accent while the pointer is over it, the cursor shows a move
+**Pass:** whatever is under the pointer washes accent while the pointer is over it, the cursor shows a move
rather than a refusal, and the drop files the host — it moves under that heading and the counts on both
headings change. Dropping onto its own group's heading is refused while still in the air.
@@ -651,16 +656,19 @@ automated. The write it performs is: `MovingAHostToAGroup_FilesItAndLeavesItSele
### 7.7 A click still selects, and a double click still connects
-Click host rows; drag one a few pixels without releasing; double-click one.
+Click host cards; drag one a few pixels without releasing; double-click one. Then double-click a group
+heading.
-**Pass:** a click selects, a small movement starts nothing, and a double click connects.
+**Pass:** a click selects, a small movement starts nothing, and a double click connects. Double-clicking a
+heading folds it and unfolds it again and connects to nothing.
-**Failure means:** the 5-pixel threshold in `HostSidebar.axaml.cs` is not doing its job — the same failure
-as 2.16 on the other screen, and here it would make the list unusable.
+**Failure means:** the 5-pixel threshold in `HostsScreen.axaml.cs` is not doing its job — the same failure
+as 2.16 on the other screen, and here it would make the grid unusable. A heading that connects means the
+double-tap handler has lost its check that the pointer was over a card.
### 7.8 The highlight clears after a drag that goes nowhere
-Drag a host over a heading and release outside the list, or press Escape mid-drag.
+Drag a host over a heading and release outside the grid, or press Escape mid-drag.
**Pass:** the wash goes away.
@@ -672,8 +680,8 @@ With host A selected, right-click host B and choose Delete.
**Pass:** no menu opens at all, and the host selection has not moved.
-**Failure means:** a menu acting on the selection rather than on the row under the pointer deletes the wrong
-machine. `HostSidebarTests` covers both halves headlessly, so this is a confirmation that a real popup
+**Failure means:** a menu acting on the selection rather than on the card under the pointer deletes the
+wrong machine. `HostGridTests` covers both halves headlessly, so this is a confirmation that a real popup
behaves as the headless one did.
### 7.10 Clicking a host in the palette connects
@@ -800,11 +808,12 @@ side of it remains unmeasurable for the reasons in phase 8.
Open a host's editor with a keychain holding a dozen tags.
-**Pass:** the editor pane scrolls, and FORGET HOST KEY is reachable at the bottom of it.
+**Pass:** the drawer scrolls, and FORGET HOST KEY is reachable at the bottom of it.
-**Failure means:** the pane's MaxHeight is gone or the ScrollViewer is. The layout harness skips anything
-inside a ScrollViewer, so from that commit on it certifies the pane fits rather than the fields — it will
-tell you the pane is fine while the last button sits below the window.
+**Failure means:** the drawer's ScrollViewer is gone. The layout harness skips anything inside one, so from
+that commit on it certifies the drawer fits rather than the fields — it will tell you the drawer is fine
+while the last button sits below the window. The editor used to carry a MaxHeight of its own because the
+host list shared its column; the drawer is alone in its column now, so the height is the window's.
### 9.2 A chip toggles and reads as toggled
diff --git a/src/DodoSSH.Client.App/App.axaml b/src/DodoSSH.Client.App/App.axaml
index 26c5500..8ad5b88 100644
--- a/src/DodoSSH.Client.App/App.axaml
+++ b/src/DodoSSH.Client.App/App.axaml
@@ -35,13 +35,40 @@
wraps in the application and does not wrap in the harness makes every measured height too small, which
is the one way this kind of test lies quietly.
-->
+
+
+
+
@@ -315,7 +342,7 @@
because a number that lit with its row would compete with the word beside it for the same emphasis.
-->
@@ -325,7 +352,7 @@
@@ -343,7 +370,7 @@
-
+
@@ -375,6 +402,19 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -602,16 +712,16 @@
-
-
+
+