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 @@ - - + +