From dbf6ce1bcfefd7067fa65fa94aca550ac100ee56 Mon Sep 17 00:00:00 2001 From: Jaap-Jan de Wit | DodoTech Date: Sat, 8 Aug 2026 23:09:57 +0200 Subject: [PATCH] Give the phone its pins: an editor section and chips on Files MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The data was never the gap — HostSecret.PinnedPaths syncs and merges on both heads, and the desktop's drawer has staged it since v5 — the phone just had nowhere to add, remove or use a pin. Now it has both halves. The host editor page gains a QUICK ACCESS section over the same shared staging the drawer binds (EditorPinnedPaths, AddEditorPin, RemoveEditorPin), with the remove target at this head's 44dp touch floor rather than the desktop's 22-pixel close box, and no folder glyph because this head embeds no icon font for one. The page also gains a Status line of its own: the add command's five refusals speak through Status, and this page covers the screen that normally draws it — a refusal nothing shows is no refusal at all. The Files screen draws the connected host's pins as chips between the breadcrumb and the listing, each running GoRemoteCommand exactly as a crumb does. They are captured at connect, like ConnectedTo and the session facts before them; a bucket gets none, having no HostSecret to pin anything on. Covered headlessly in ShellFlowTests — connect populates, disconnect clears, a bucket stays empty — and by manual checks 8.18 and 8.19, whose phase preamble also stops claiming thirteen checks when it lists twenty-one. --- docs/design-import-gaps.md | 2 +- docs/manual-checks.md | 56 +++++++- .../Views/FilesScreen.axaml | 46 ++++++- .../Views/HostsScreen.axaml | 66 ++++++++++ .../ViewModels/TransfersViewModel.cs | 40 ++++++ .../ShellFlowTests.cs | 124 ++++++++++++++++++ 6 files changed, 329 insertions(+), 5 deletions(-) diff --git a/docs/design-import-gaps.md b/docs/design-import-gaps.md index 96416a6..6216db9 100644 --- a/docs/design-import-gaps.md +++ b/docs/design-import-gaps.md @@ -258,7 +258,7 @@ the chrome, hosts and terminals, file transfer, the vault, teams, and preference > | The host card as a link straight to a terminal | Click still selects, double-click still connects, and the pencil still opens the pane — the mock's card-as-link is not adopted, because multi-select (Ctrl, Shift, the marquee band) depends on a plain click meaning "choose this one" rather than "go". | > | Quick connect's SSH/SFTP kind column | The auth word — credential, key, or password — see above. | > | A collapsed section staying collapsed after a restart | In memory only, for the running session. `settings.json` holds two scalars by decision — the terminal's text size and whether this machine checks for updates on its own — and collapse state is not judged worth a third. | -> | QUICK ACCESS's editor, on the phone | **Deferred; the data is not.** `HostSecret.PinnedPaths` is shared, synced and merged on both heads, so a pin made on the desktop reaches the phone and back — Android just has nowhere yet to add or remove one itself. | +> | QUICK ACCESS's editor, on the phone | ◆ **Shipped, over the same shared data the deferral above described.** The phone's host editor draws its own QUICK ACCESS section on the same staged `VaultViewModel.EditorPinnedPaths` the desktop's drawer binds — a row per pin, an add field and button, and a remove target sized to this head's 44dp touch floor rather than the desktop's 22-pixel close box. `AddEditorPinCommand`'s refusals surface through a `Status` line the editor page draws for itself, since that page covers the whole screen and the list behind it draws its own `Status` off-screen for as long as it is open. The pins themselves reach a second surface this head has that the desktop does not need: `TransfersViewModel.ConnectedPinnedPaths` carries them as chips on the Files screen while connected, and tapping one runs `GoRemoteCommand` — the same command the breadcrumb trail already used to navigate. Two deviations from the desktop, both named where they land: the chip row is a snapshot taken at connect rather than a live follow of the vault, so a pin edited mid-session shows up on the next connect rather than this one; and the editor's own hint sentence says the pins appear on the Files screen, not above a terminal — this head has no terminal strip for them to sit above, the same honesty the row below already states for the desktop's own hint. | > | Collapse All beside every section's own collapse chevron | Bound on every heading's view model and shown on only the first — `SidebarGroupHeader.IsFirstBoardSection` is what a virtualised list of sections uses in place of a control of the board's own that would otherwise have to sit above all of them. | > | The QUICK ACCESS hint's claim that pins live in a sidebar | "Pinned folders appear above the terminal for this host." — no sidebar exists on this screen for the sentence to point at, so the shipped hint says where they actually draw. | > | The mock's "Saving to **DodoTech ▾** vault" subtitle, with a picker's chevron inside a sentence | The pre-existing `DrawerSubtitle` wording — the vault's name alone, unchanged by this pass. A chevron inside running text implies the text itself is the control, which it is not: the vault picker is its own element, shown only while creating and only above one writable vault, as it always has been. | diff --git a/docs/manual-checks.md b/docs/manual-checks.md index 46926ea..e824381 100644 --- a/docs/manual-checks.md +++ b/docs/manual-checks.md @@ -1101,7 +1101,7 @@ time)" — is the worse failure of the two: it rebinds a host as a side effect o ## Phase 8 — Adding and removing on the phone's host list -Thirteen checks, and the reason there are thirteen rather than none is worth stating: **the layout suite +Twenty-one checks, and the reason there are twenty-one rather than none is worth stating: **the layout suite cannot see any of this and structurally never will.** `DodoSSH.Client.App.Layout.Tests` targets `net10.0` and `DodoSSH.Client.Android` targets `net10.0-android`, so a project reference is impossible; Avalonia's application, dispatcher and platform are one-shot process globals, so a second head cannot share the @@ -1418,6 +1418,60 @@ destinations, so switching under a live one would show a screen titled S3 listin the row — that screen keeps its own copy of the host list, so it has to be re-found there by entity id rather than handed the vault's object. +### 8.18 QUICK ACCESS in the phone's host editor + +Open a host's editor and scroll to QUICK ACCESS. + +**Pass:** an empty list, an add field and an ADD button. Type `/var/www/app` and press ADD. + +**Pass:** a row appears carrying that path and a ✕ at least 44dp on a side. Type the same path again and +press ADD. + +**Pass:** nothing is added, and a sentence appears on the page saying the path is already pinned — this page +covers the whole screen while it is open, so that sentence is this page's own `Status` line rather than the +one the host list draws above HOSTS, which is off-screen right now. Clear the box and press ADD with nothing +typed. + +**Pass:** a sentence saying a pinned path cannot be blank, in the same place. + +Press the ✕ on the pinned row, then SAVE. + +**Pass:** back on HOSTS with the pin gone. Open the editor on that host again. + +**Pass:** QUICK ACCESS is empty — the removal was saved, not merely staged. Re-pin `/var/www/app`, SAVE, and +check the same host on the desktop. + +**Pass:** the pin is there. `HostSecret.PinnedPaths` is shared and merged like every other field on a host, +so nothing about this page keeps its own copy. + +**Failure means:** a refusal that changes nothing on screen is `AddEditorPinCommand` writing to `Status` +with nothing on this page bound to it — the honesty rule broken silently, since the command still behaves +correctly and only the telling of it is missing. A pin gone after SAVE-then-reopen but present on the +desktop is `BuildHost` not reading `EditorPinnedPaths`, or `EditSelectedHost` not loading it back in. + +### 8.19 Pin chips on the Files screen + +Pin a folder on a host, then connect to it on the files screen (SFTP), either directly or via **Connect via +SFTP**. + +**Pass:** once connected, a row of chips appears between the breadcrumb and the listing, one per pin, each +at least 44dp tall. Tap one. + +**Pass:** the listing navigates straight to that directory, the same as tapping a breadcrumb crumb does. +Disconnect, then connect to a host with nothing pinned. + +**Pass:** no chip row at all — not an empty one. Connect to a bucket instead. + +**Pass:** still no chip row, on any bucket. A bucket has no `HostSecret` underneath it and so nothing to +pin. + +**Failure means:** chips that do not move the listing are the row's `GoRemoteCommand` binding pointed at the +wrong `DataContext` — see the `$parent[views:FilesScreen]` escape every other command in this file uses. A +chip row surviving a disconnect, or appearing under a bucket, is `TransfersViewModel.ConnectedPinnedPaths` +not being cleared in `CloseSessionAsync` or `OpenBucketAsync`. A chip row missing a pin added *after* this +connect is not a bug — see `ConnectedPinnedPaths`'s own remark on why this is a snapshot rather than a live +follow, and try disconnecting and reconnecting instead. + --- ## Phase 9 — Tag chips and the picker diff --git a/src/DodoSSH.Client.Android/Views/FilesScreen.axaml b/src/DodoSSH.Client.Android/Views/FilesScreen.axaml index 7230dfe..5651ed2 100644 --- a/src/DodoSSH.Client.Android/Views/FilesScreen.axaml +++ b/src/DodoSSH.Client.Android/Views/FilesScreen.axaml @@ -48,7 +48,7 @@ the refusal has no continue button here either. --> - + @@ -199,8 +199,48 @@ + + + + + + + + + + + + + + + + - + @@ -266,7 +306,7 @@ - diff --git a/src/DodoSSH.Client.Android/Views/HostsScreen.axaml b/src/DodoSSH.Client.Android/Views/HostsScreen.axaml index f4360ea..17ad1fa 100644 --- a/src/DodoSSH.Client.Android/Views/HostsScreen.axaml +++ b/src/DodoSSH.Client.Android/Views/HostsScreen.axaml @@ -1098,6 +1098,60 @@ Command="{Binding AddEditorTagCommand}" /> + + + + + + + + + + + + +