From 80ae586fc41291f48ee3fb63c2fe941e8ff109b8 Mon Sep 17 00:00:00 2001 From: Jaap-Jan de Wit | DodoTech Date: Mon, 3 Aug 2026 14:33:16 +0200 Subject: [PATCH] Give a shell the whole phone, and one bar to leave it by MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A connected phone was drawing five rows of chrome around the thing the user opened it for. The vault header at 56, the terminal's own tab strip at 52, a connection line at 36, the shells strip at 46 and the four-entry bottom bar at 64: at 360dp that is about a third of the display, and every row of it was about somewhere the user was not. What replaces them is one 52-pixel bar drawn by the surface itself — back on the left, the session pills, and a `+` across from them — and then the terminal. Three of those rows belong to `PhoneShell` and each is now bound on `IsShowingPages`. That is the same question asked once rather than three conditions that could drift: the surface is either a page or a terminal, and these are the chrome a page has. The header needed a wrapper because Avalonia's bindings have no "and" and it already had a condition of its own; the strip needed one for the same reason. The bottom bar had none and is bound directly. The back arrow goes to the page the terminal was opened over rather than to Hosts by name, because the system back gesture already picks that and an arrow landing somewhere else would be the second of two answers to one question. The bar's `+` raises a sheet offering the three connections this application can make — a shell, a host's files over SFTP, a bucket — since SFTP and S3 used to be two taps through the bottom bar's MORE and the bar is not on screen here. A control that replaced it and led to one of the three would have quietly removed the other two. Two things moved rather than being dropped. The text-size buttons are pinned at the right-hand end of the accessory key row, outside its scroller: the connection line existed to keep them from scrolling out of reach, and being outside the scroller answers that argument rather than abandoning it. The dialled address moved onto the connecting card, which is the moment it is worth reading — what is being connected to, before anything has answered — and after that the shell's own prompt says it more accurately than a header derived from the keychain ever did. The sheet collapses the renderer rather than covering it. Whether Android's `WebView` composites above Avalonia content the way Win32's child window does is still unverified — `docs/android-port.md` has said so since the port — so this follows the desktop's palette and gives up the rectangle outright, which is correct under either answer. It collapses `IsTerminalShowing` and not `IsTerminalSurface`, because the bar the sheet was raised from is part of that surface and dropping it would take the bar, the tabs and the whole arrangement with it, leaving the sheet floating over the page underneath. `OnSurfaceChanged` is the one place the flag is lowered, and that is the load- bearing half. Every way out of a terminal ends there — a destination, the files screen, the palette connecting to a host, closing the last tab, a lock — and each of them would otherwise leave a sheet set over a page. Not merely untidy: the flag holds the renderer blank, so the next return to the terminal would draw the menu again over a rectangle kept blank by it. Opening is refused off the terminal surface for the same reason from the other direction. The back gesture gains a guard above the switch, in the shape of the editor guard that arrived with the phone's `+`. It is nearer than any of them: with no header and no bottom bar, while the menu is up that gesture is the only way off it other than the scrim and CANCEL. The bottom bar's Terminal entry lost its `IsCurrent` binding. The bar is collapsed on that surface, so the binding could only ever be read as false, and a rule about a state the control cannot be in is a claim that it can. Three tests in `ShellFlowTests`, which is where shared state-machine behaviour for this head goes: the collapse and its recovery, the refusal to open over a page, and the sheet lowering both by a menu entry and by a route it was never wired to. Everything visual needs a device, so it is phase 11 of `docs/manual-checks.md` — and 11.2 is the check that would finally settle the compositing question this head has carried as unverified since the port. --- README.md | 6 + docs/android-port.md | 8 + docs/design-import-gaps.md | 2 +- docs/manual-checks.md | 63 ++++ .../Views/PhoneShell.axaml | 172 +++++---- .../Views/PhoneShell.axaml.cs | 18 + .../Views/TerminalScreen.axaml | 338 ++++++++++++------ .../ViewModels/MainWindowViewModel.cs | 80 ++++- .../ShellFlowTests.cs | 76 ++++ 9 files changed, 591 insertions(+), 172 deletions(-) diff --git a/README.md b/README.md index 7bb53b7..2bf19a7 100644 --- a/README.md +++ b/README.md @@ -274,6 +274,12 @@ that design now; the desktop's own v2 is a 190-pixel labelled sidebar in place o 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. +**A connected phone shows one bar and then the terminal.** The header, the session strip and the bottom +bar are collapsed while a shell is up, and a single 52-pixel row replaces them: back on the left, the +sessions as pills, and a `+` on the right offering the three connections this application can make — a +shell, a host's files over SFTP, or a bucket. The system back gesture does what the arrow does, and lowers +that menu first if it is open. + Widening the sidebar moved the desktop window's minimum from `880x560` to `1016x574`, which leaves every screen exactly the width it was designed against. diff --git a/docs/android-port.md b/docs/android-port.md index e07119a..2b3933a 100644 --- a/docs/android-port.md +++ b/docs/android-port.md @@ -508,6 +508,14 @@ go at 360dp: drawn, because a modifier that is on and does not look on is how somebody sends `^L` to a database prompt believing they typed an `l`. + **The surface has since taken the whole screen.** `PhoneShell` collapses the header, the session strip + and the bottom bar while a terminal is showing — one binding on `IsShowingPages` each — and the screen + draws a 52-pixel bar in their place: back, the session pills, and a `+` raising a sheet with the three + connections there are. That sheet is the head's first control that could be drawn over the renderer, so + it collapses it rather than covering it, exactly as the desktop's palette does; whether Android's + WebView actually composites above Avalonia content is still the unverified question recorded below, and + collapsing is correct under either answer. + All eight are done for the decided scope, and v2 has since gone past it — see the note under step 7. What is left, in the order it matters: diff --git a/docs/design-import-gaps.md b/docs/design-import-gaps.md index 336c364..1b6b98b 100644 --- a/docs/design-import-gaps.md +++ b/docs/design-import-gaps.md @@ -24,7 +24,7 @@ the chrome, hosts and terminals, file transfer, the vault, teams, and preference > | v2 element | What ships instead | > | --- | --- | > | The **FORWARDING** screen: local/remote/dynamic rules, toggles, bytes transferred | **Nothing, said out loud.** `ISshConnection` offers `OpenShellAsync` and nothing else, so there is no tunnel for a rule to run through; `SyncEntityType.PortForward = 9` is still reserved and still unused. The MORE screen carries a paragraph naming the absence, for the reason the desktop keeps TEAMS in its rail. | -> | `23 ms · fwd 5432` on the terminal's connection line | The account and endpoint actually dialled. There is no RTT measurement in SSH.NET and nothing forwards anything. | +> | `23 ms · fwd 5432` on the terminal's connection line | ◆ **The line is gone, and what was real on it moved.** There was never an RTT to draw — SSH.NET measures none — and nothing forwards anything, so what shipped was the account and endpoint actually dialled. In v3 a connected phone draws one 52-pixel bar and then the terminal, so a second 36-pixel row naming the machine is exactly the chrome that surface exists to give back: the address is on the connecting card, where it is read before anything has answered, and the shell's own prompt says it afterwards. The two text-size buttons that shared the line are pinned at the end of the accessory row, outside its scroller, which is what the line was protecting them from. | > | `ED25519` badge and `SHA256:kQ9f…Zw2M` on every keychain card | `Detail`, which is what is genuinely known *about* an item. Unchanged from the first import: no algorithm field, no fingerprint, and computing either means parsing armour the type stores verbatim. | > | An `agent` chip on a key | Omitted. There is no agent of any kind — see the first import's Vault section. | > | Snippet cards footed with `edge-eu-1 · today 10:58 · exit 0` | The command and its badge. Nothing records where a snippet ran, when, or what it returned; the shell it is typed into never reports back. | diff --git a/docs/manual-checks.md b/docs/manual-checks.md index 0acef2e..9027715 100644 --- a/docs/manual-checks.md +++ b/docs/manual-checks.md @@ -889,3 +889,66 @@ Focus a passphrase box, then turn the phone sideways. **Pass:** the box is still visible and still focused, and the shell is intact — the activity handles the rotation rather than being recreated, and live shells survive it. + +--- + +## Phase 11 — The phone's terminal surface + +Every check here needs a real device for the reason Phase 10's do, plus one of its own: the interesting +question on this screen is whether a native `WebView` composites above what Avalonia draws over it, and no +headless surface has a native view to answer with. `docs/android-port.md` still records that as unverified; +11.2 is the check that settles it. + +### 11.1 A shell gets the screen · **the important one for chrome** + +Open a shell from HOSTS. + +**Pass:** the vault header, the session strip and the four-entry bottom bar are all gone. What is left is +one bar — a back arrow, the session pills, a `+` — and then the terminal down to the accessory keys. Press +back: all three come back, the tab is still in the strip and its dot is still green. + +**Failure means:** one of the three rows is not bound on `IsShowingPages`, or the terminal is being reached +by a route that leaves `Surface` on `Page`. + +### 11.2 The connect menu is not drawn over the renderer · **the important one** + +With a shell showing output, press `+`. + +**Pass:** the terminal's rectangle goes to the canvas colour and the sheet sits over it whole — scrim, three +rows and CANCEL, every one of them tappable, none of them sliced down the left edge. Tap the scrim: the +terminal comes back with its scrollback intact and the shell still running. + +**Failure means:** `IsTerminalShowing` is not being cleared by `IsConnectSheetOpen` — or, if the sheet is +sliced *despite* the rectangle going blank, something else in that Panel is still showing. A sheet that +draws over live terminal output is the Android answer to the compositing question, and it means every +future sheet on this surface has to collapse the renderer too. + +### 11.3 Back lowers the menu before it leaves the terminal + +With the connect menu open, use the system back gesture. + +**Pass:** the menu closes and the terminal is still showing. A second back leaves the terminal for the +screen it was opened over. + +**Failure means:** the guard in `PhoneShell.axaml.cs` is below the surface check rather than above it, and +one gesture is spending two levels. + +### 11.4 The two end buttons cannot be pushed off the bar + +Open six or more shells. + +**Pass:** the pills scroll under a fixed back arrow and a fixed `+`; neither ever leaves the screen, and +scrolling the pills to either end does not move them. + +**Failure means:** a control was put inside the `ScrollViewer` rather than beside it. + +### 11.5 The text-size buttons are always reachable + +With a shell open, scroll the accessory key row to the far left and the far right. + +**Pass:** `A−` and `A+` stay pinned at the right-hand end throughout, separated from the keys by the +hairline, and both are at least 38 tall. At the smallest and largest sizes the one that can do nothing is +visibly disabled rather than silently inert. + +**Failure means:** they have been folded into the scrolling row — which is the arrangement the connection +line existed to avoid, and the reason it could be removed at all. diff --git a/src/DodoSSH.Client.Android/Views/PhoneShell.axaml b/src/DodoSSH.Client.Android/Views/PhoneShell.axaml index b774a53..a13c271 100644 --- a/src/DodoSSH.Client.Android/Views/PhoneShell.axaml +++ b/src/DodoSSH.Client.Android/Views/PhoneShell.axaml @@ -21,6 +21,16 @@ The order is the design's rather than the rail's. Terminal sits second, beside Hosts, because those two are the pair a session moves between; on the desktop the terminal is not a rail entry at all. + + ── a terminal gets the screen ───────────────────────────────────────────────────────────────────────── + Three of the four rows below stand down while a shell is showing: the header, the shells strip and the + bottom bar itself. All three are bound on IsShowingPages, which is the same question asked once — the + surface is either a page or a terminal, and these are the chrome a page has. + + The arithmetic is why. Header 56, strip 46, bar 64, and the terminal's own two rows on top of that: at + 360dp the shell was framed by about a third of the display, all of it about somewhere the user was not. + What takes their place is one 52-pixel bar drawn by the surface itself, carrying back on the left and + the sessions and a + across from it. See TerminalScreen.axaml. --> - - + + + - - - - + + + + - + - - - - - + + + + + - - - + + + + @@ -180,41 +199,57 @@ v2 draws the sessions as pills rather than as a labelled row, and drops the word SHELLS: with a rounded chip carrying a live dot and a name, the label was spending nine characters of a 360dp row saying what the row already looks like. + + On every screen except the one it names. The terminal draws these same sessions in its own bar, and + two rows of the same pills — one of them 46 pixels of it — is the arrangement this surface exists to + stop. Wrapped rather than given a second condition, because the strip's own visibility is about + whether there are any tabs and this one is about which surface is up. --> - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + - + @@ -225,8 +260,13 @@ - - + - + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +