Android: the redesign — a Vaults tab with hosts, keychain and snippets, card lists, a keychain that can add, and files that act on what is chosen
ci / build and test (push) Successful in 3m49s
ci / android head (push) Successful in 3m55s
ci / api image (push) Successful in 6s
ci / desktop nightly (push) Successful in 1m22s

The bottom bar is Vaults · Connections · Settings, drawn as Material 3 pills with icons from the embedded
Material font and a count of open sessions on Connections, which replaces the strip of shells above the
bar. Vaults is the open vault: its header carries the name as a switch to the vaults screen, the sync
light and a lock icon, and a Hosts · Keychain · Snippets segmented control sits under it. The keychain
and snippets move there from Settings (IsVaultSurface lights the tab; IsMoreSurface no longer lists
them), and back from either goes to the hosts. Settings becomes an account card and grouped sections:
security, vaults and history, files, this app.

Host rows are cards with a terminal tile and the live dot on its corner. The connect sheet names the
host and its address, labels its box, focuses it as it opens, reveals the password on request, and puts
CANCEL left and CONNECT right; every two-button row on the head now confirms on the right. While a
hosts-screen sheet is up the header and bar are dimmed too and a tap on them lowers it, and the bar
stands down while the software keyboard is up.

The keychain gains a floating + and a sheet of the desktop's five adds (generate a key, paste a key, a
password, a bucket, a tag) over the editors VaultViewModel already had, drawn as a full-screen page with
labels above the boxes; a chosen item gets EDIT and COPY PUBLIC KEY beside MOVE and DELETE. Snippets get
a floating + and cards that say RUNS or INSERTS. Files trade the seven-button block for a selection bar
(save, and a ⋮ sheet with rename, move, permissions and a red delete), folder and file tiles, an upload
button, and header actions for a terminal on the same host, refresh and close; a terminal's + sheet
opens SFTP on that terminal's own host.

The theme takes the audit's visual findings: Fluent's ClipToBounds is cleared on glowing buttons so the
glow is no longer cut to a square (the desktop's Button.accent too), prose is Montserrat with mono kept
for addresses and code, disabled secondary buttons are quieter than enabled ones, Fluent's dropdown,
checkbox, switch, focus and selection brushes come from the palette, touch no longer leaves hover fills
behind, and lists, nav entries and icon buttons carry names for TalkBack.

Builds clean; checked on the emulator end to end: unlock, the three vault views, adding and deleting a
password, connecting through the new sheet, files on the terminal's host with the selection bar and
its sheet. App tests 727 of 733 pass with 5 skipped; the one failure, TheCountsAPhoneShows_
FollowEveryForwardsState, passes three runs of three alone and is untouched here. Layout tests pass.
This commit is contained in:
jaap-jan committed 2026-10-04 15:50:09 +02:00
1 parent 6806e542f9
commit 5b32c76c95
24 files changed
+2022 -654

No files matched your search

@@ -73,17 +73,28 @@
<Setter Property="Background" Value="{StaticResource Active}" />
</Style>
<!--
◆ Material icons rather than Unicode glyphs, as the desktop's own rail draws them. The glyphs came from
whatever symbol font the phone happened to have, at whatever weight and optical size it drew them — the
heavy ❯ beside the hairline ◳ the 2026-10-02 audit pointed at (M7) — and two of them meant two things:
⚙ was both the Settings tab and Preferences, ⚿ both the keychain and locking it. The icon font is the
one the desktop embeds, so one destination now has one mark on both heads.
-->
<Style Selector="Button.railentry TextBlock.railglyph">
<Setter Property="Foreground" Value="{StaticResource TextDim}" />
<Setter Property="FontSize" Value="14" />
<Setter Property="FontFamily" Value="{StaticResource IconFont}" />
<Setter Property="FontSize" Value="19" />
<!-- A ligature codepoint is noise to a screen reader; the entry's name is on the button. -->
<Setter Property="AutomationProperties.AccessibilityView" Value="Raw" />
<Setter Property="Width" Value="24" />
<Setter Property="VerticalAlignment" Value="Center" />
</Style>
<!-- A destination's name is a word rather than an identifier, so it takes the sans face — see the remark
on TextBlock.body in Theme/Phone.axaml. -->
<Style Selector="Button.railentry TextBlock.raillabel">
<Setter Property="Foreground" Value="{StaticResource TextDim}" />
<Setter Property="FontFamily" Value="{StaticResource MonoFont}" />
<Setter Property="FontSize" Value="12.5" />
<Setter Property="FontSize" Value="13.5" />
<Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="TextTrimming" Value="CharacterEllipsis" />
</Style>
@@ -120,11 +131,11 @@
<DockPanel LastChildFill="False">
<StackPanel DockPanel.Dock="Bottom" Margin="8,0,8,10">
<Button Classes="railentry" Classes.active="{Binding IsPreferencesShowing}"
<Button Classes="railentry" AutomationProperties.Name="Preferences" Classes.active="{Binding IsPreferencesShowing}"
Command="{Binding ShowScreenCommand}"
CommandParameter="{x:Static vm:ShellScreen.Preferences}">
<Grid ColumnDefinitions="Auto,*,Auto">
<TextBlock Grid.Column="0" Classes="railglyph" Text="⚙" />
<TextBlock Grid.Column="0" Classes="railglyph" Text="&#xE429;" />
<TextBlock Grid.Column="1" Classes="raillabel" Text="Preferences" />
</Grid>
</Button>
@@ -134,11 +145,11 @@
HorizontalScrollBarVisibility="Disabled">
<StackPanel Margin="8,10,8,0" Spacing="2">
<Button Classes="railentry" Classes.active="{Binding IsHostsShowing}"
<Button Classes="railentry" AutomationProperties.Name="Hosts" Classes.active="{Binding IsHostsShowing}"
Command="{Binding ShowScreenCommand}"
CommandParameter="{x:Static vm:ShellScreen.Hosts}">
<Grid ColumnDefinitions="Auto,*,Auto">
<TextBlock Grid.Column="0" Classes="railglyph" Text="▤" />
<TextBlock Grid.Column="0" Classes="railglyph" Text="&#xE875;" />
<TextBlock Grid.Column="1" Classes="raillabel" Text="Hosts" />
<TextBlock Grid.Column="2" Classes="railcount" Text="{Binding Vault.Hosts.Count}" />
</Grid>
@@ -155,67 +166,67 @@
surface and is that count, drawn as the sessions themselves — a number beside this word would be
the same fact said twice, three inches apart.
-->
<Button Classes="railentry" Classes.active="{Binding IsTerminalSurface}"
<Button Classes="railentry" AutomationProperties.Name="Connections" Classes.active="{Binding IsTerminalSurface}"
Command="{Binding ShowTerminalCommand}">
<Grid ColumnDefinitions="Auto,*,Auto">
<TextBlock Grid.Column="0" Classes="railglyph" Text="⌗" />
<TextBlock Grid.Column="0" Classes="railglyph" Text="&#xEB8E;" />
<TextBlock Grid.Column="1" Classes="raillabel" Text="Connections" />
</Grid>
</Button>
<Border Height="1" Background="{StaticResource Border}" Margin="4,8" />
<Button Classes="railentry" Classes.active="{Binding IsKeychainShowing}"
<Button Classes="railentry" AutomationProperties.Name="Keychain" Classes.active="{Binding IsKeychainShowing}"
Command="{Binding ShowScreenCommand}"
CommandParameter="{x:Static vm:ShellScreen.Keychain}">
<Grid ColumnDefinitions="Auto,*,Auto">
<TextBlock Grid.Column="0" Classes="railglyph" Text="⚿" />
<TextBlock Grid.Column="0" Classes="railglyph" Text="&#xE73C;" />
<TextBlock Grid.Column="1" Classes="raillabel" Text="Keychain" />
<TextBlock Grid.Column="2" Classes="railcount" Text="{Binding Vault.Keys.Count}" />
</Grid>
</Button>
<Button Classes="railentry" Classes.active="{Binding IsSnippetsShowing}"
<Button Classes="railentry" AutomationProperties.Name="Snippets" Classes.active="{Binding IsSnippetsShowing}"
Command="{Binding ShowScreenCommand}"
CommandParameter="{x:Static vm:ShellScreen.Snippets}">
<Grid ColumnDefinitions="Auto,*,Auto">
<TextBlock Grid.Column="0" Classes="railglyph" Text="❯" />
<TextBlock Grid.Column="0" Classes="railglyph" Text="&#xE86F;" />
<TextBlock Grid.Column="1" Classes="raillabel" Text="Snippets" />
</Grid>
</Button>
<Button Classes="railentry" Classes.active="{Binding IsTransfersShowing}"
<Button Classes="railentry" AutomationProperties.Name="SFTP" Classes.active="{Binding IsTransfersShowing}"
Command="{Binding ShowFilesCommand}"
CommandParameter="{x:Static vm:RemoteKind.Host}">
<Grid ColumnDefinitions="Auto,*,Auto">
<TextBlock Grid.Column="0" Classes="railglyph" Text="⇅" />
<TextBlock Grid.Column="0" Classes="railglyph" Text="&#xE2C7;" />
<TextBlock Grid.Column="1" Classes="raillabel" Text="SFTP" />
</Grid>
</Button>
<Button Classes="railentry" Classes.active="{Binding IsBucketsShowing}"
<Button Classes="railentry" AutomationProperties.Name="S3" Classes.active="{Binding IsBucketsShowing}"
Command="{Binding ShowFilesCommand}"
CommandParameter="{x:Static vm:RemoteKind.Bucket}">
<Grid ColumnDefinitions="Auto,*,Auto">
<TextBlock Grid.Column="0" Classes="railglyph" Text="◳" />
<TextBlock Grid.Column="0" Classes="railglyph" Text="&#xE2BD;" />
<TextBlock Grid.Column="1" Classes="raillabel" Text="S3" />
</Grid>
</Button>
<Button Classes="railentry" Classes.active="{Binding IsLogsShowing}"
<Button Classes="railentry" AutomationProperties.Name="Logs" Classes.active="{Binding IsLogsShowing}"
Command="{Binding ShowScreenCommand}"
CommandParameter="{x:Static vm:ShellScreen.Logs}">
<Grid ColumnDefinitions="Auto,*,Auto">
<TextBlock Grid.Column="0" Classes="railglyph" Text="≡" />
<TextBlock Grid.Column="0" Classes="railglyph" Text="&#xEF6E;" />
<TextBlock Grid.Column="1" Classes="raillabel" Text="Logs" />
</Grid>
</Button>
<Button Classes="railentry" Classes.active="{Binding IsVaultsShowing}"
<Button Classes="railentry" AutomationProperties.Name="Vaults" Classes.active="{Binding IsVaultsShowing}"
Command="{Binding ShowScreenCommand}"
CommandParameter="{x:Static vm:ShellScreen.Vaults}">
<Grid ColumnDefinitions="Auto,*,Auto">
<TextBlock Grid.Column="0" Classes="railglyph" Text="◎" />
<TextBlock Grid.Column="0" Classes="railglyph" Text="&#xE1A1;" />
<TextBlock Grid.Column="1" Classes="raillabel" Text="Vaults" />
</Grid>
</Button>