Public Access
Give the desktop the second design too, and the window the size it now needs
The desktop v2 design is the other half of the one the phone took last commit, and this is its chrome: a 190-pixel labelled sidebar where the 54-pixel icon rail was, a titlebar with the search box centred in it, and session tabs drawn as pills. The palette was already here — it is shared, and moved when the phone's did — so what this changes is shape rather than colour. **The window's minimum grew, and by exactly what v2 added.** The sidebar is 136 wider and the chrome 14 taller, so 880x560 became 1016x574. That is not a round number somebody liked: it leaves every screen the same 826x464 it was designed against, which is the arithmetic the layout suite is built on. Four of the tables stop fitting at 690 wide, so widening the sidebar and leaving the window alone would have broken them somewhere no test was looking. LayoutHarness carries the new constants and the suite still passes at the minimum, which is the whole reason it exists. The rail's five-character abbreviations are gone with the width that caused them — PINS and SNIPS are Pins and Snippets again — and each row gains a glyph and a count. A count is drawn only where one is real, so SFTP, Logs and Preferences show nothing rather than a zero: a transfer queue's depth is not how many files a screen holds, and a log has no total until it is read. The count beside Pins is the vault's own, not the Pins screen's VisiblePins, which is the filtered list and would have made the sidebar count whatever somebody had just typed into a filter box on another screen. Teams has no count for a related reason: they are read from the server when that screen is opened rather than on unlock, so a number there would read 0 until somebody had already been to look. One colour moved with it, finishing what the repalette started: the live-session summaries on the unlock and sign-out cards were Info, so the two heads disagreed about a fact the phone paints green. They match again. **Buckets became a destination rather than a mode**, which is what the design draws and what the phone already does. The HOST / BUCKET pair inside the files screen is gone; ShellScreen.Buckets draws the same TransfersScreen with the other picker, and the sidebar entry is what sets it. That also settles an old disagreement rather than merely moving it: TotalItemCount is keys plus passwords and excludes buckets, so the number beside the keychain used to disagree with the list under it, and now counts what that screen shows. There is one session behind both file destinations, so asking for the other kind while something is open is refused rather than obeyed — and refusing means staying put. An earlier turn of this had it move anyway and only decline to switch the picker, which put the S3 entry in the sidebar over a screen still listing an SFTP host: two pieces of chrome disagreeing about where you are, which is worse than the navigation simply not happening. The message that says so goes to Transfers.Status, which turned out to be drawn in the same grid cell as the connected chip — survivable while it was mostly read before connecting, and not once a refusal reports itself there. It has its own column now. The design has nine entries' worth of screens and draws five. Pins, Teams, Import and Preferences are built, working screens, so they keep their entries — the sidebar is labelled now and has the room, and dropping an entry would have stranded a screen rather than simplified anything. The Team vault card the design pins to the foot is not drawn: it is a second route to a screen already in the list, carrying a seat count nothing here produces. **The status bar survives the design that deletes it**, cut down to one thing. Two of the three facts it carried moved into the titlebar with v2 — the sync word is beside its dot and the shortcut hint is inside the box that uses it — so those are gone from it rather than printed twice. The third is Vault.Status, the only channel this application has for saying a save failed or a merge picked a winner. The design is a mock-up of an afternoon that goes well and has nowhere to put a sentence like that; dropping the bar would have meant dropping the sentence or repeating it on nine screens. What v2 draws and this does not is in docs/design-import-gaps.md, and it is the same list as the phone's for the same reasons: the forwarding screen and both its chips, the host detail's fingerprint, tags and last-session cards, the keychain's rotate button, the logs' FOLLOW pill and severity filters, and the session footer's latency. The terminal is not inset behind a rounded frame either — it is a native child window that composites above everything Avalonia paints, so the frame would clip nothing, which is the same answer the phone gave. **The light theme is not built.** Its accent is #6D5AE6, a different hue rather than a tint of the dark one, so it needs every colour doubled, a variant to switch on, the renderer's own page switching with it, and contrast checked twice. That is a piece of work rather than a setting, and it is separable from the layout — which is why this commit is the layout. The screens themselves are restyled through the shared vocabulary rather than rebuilt: corner radii, chips, cards and the accent's ink, all in App.axaml, so every screen moves at once. Their layouts are left alone deliberately. The design draws read-only detail panes and these screens carry the editors and forms it has no equivalent of, so replacing a layout with the mock-up's would have lost the half that is actually used. Verified by the whole suite: 1309 tests over nineteen projects, none failing, including the 68 layout cases that stand up real Avalonia and measure every screen at the new minimum. Both heads build. Not run on a machine with a display — see docs/manual-checks.md for what wants looking at. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AZE3u99BNt6LzgTC5jhbz2
This commit is contained in:
@@ -5,13 +5,28 @@
|
||||
x:DataType="vm:MainWindowViewModel">
|
||||
|
||||
<!--
|
||||
Six destinations down the left edge.
|
||||
The window's destinations, down the left edge.
|
||||
|
||||
One of them — TEAM — reaches a screen that says it is not built. It is in the rail anyway rather than
|
||||
dropped, and the reasoning is in ShellScreen: the milestones are public, the screen behind it says
|
||||
plainly what is missing, and a rail that quietly had four entries would make sharing look like a change
|
||||
of product rather than the next milestone. It is recorded in docs/design-import-gaps.md. FILES was the
|
||||
other one until M2 built it.
|
||||
Still called NavRail although v2 makes it 190 pixels wide and gives every entry a word: the type is
|
||||
named in MainWindow and in the layout suite, and "the list of places this window goes" is what it was
|
||||
called when it was 54 pixels and is what it still is. The width is the thing that changed, not the job.
|
||||
|
||||
── v2 ────────────────────────────────────────────────────────────────────────────────────────────────
|
||||
Three things the extra 136 pixels buy, and they are why the design widened it rather than a matter of
|
||||
taste. The five-character abbreviations are gone — PINS and SNIPS were a width constraint and are now
|
||||
Pins and Snippets. Each entry carries a glyph, so the list can be scanned by shape as well as read. And
|
||||
each carries a count, which is the one genuinely new fact: how many hosts, how many keys, how many pins
|
||||
is a question you would otherwise have to open the screen to answer.
|
||||
|
||||
A count is drawn only where one is real. SFTP, Logs and Preferences have none — a transfer queue's depth
|
||||
is not "how many files this screen holds", a log has no total until it is read, and preferences are not
|
||||
counted — so those three show nothing rather than a zero. The design draws a number on every row; a zero
|
||||
beside Logs would be a fact this application never computed.
|
||||
|
||||
One of the destinations — TEAM — reaches a screen that says it is not built. It is in the list anyway
|
||||
rather than dropped, and the reasoning is in ShellScreen: the milestones are public, the screen behind it
|
||||
says plainly what is missing, and a list that quietly had fewer entries would make sharing look like a
|
||||
change of product rather than the next milestone. FILES was the other one until M2 built it.
|
||||
|
||||
Buttons rather than a TabStrip or a ListBox, for the same reason the vault's category rail is: all three
|
||||
of those hold the selection themselves, so a click moves the highlight before the shell can decide
|
||||
@@ -19,57 +34,140 @@
|
||||
|
||||
Lit from IsXShowing and not from IsXScreen, which are different questions now that the tab strip spans
|
||||
every screen. A terminal opened from here leaves Screen on Hosts — deliberately, so closing the tab comes
|
||||
back — and a rail entry lit while a terminal filled the window would be pointing at a screen that is not
|
||||
back — and an entry lit while a terminal filled the window would be pointing at a screen that is not
|
||||
showing. So nothing here is lit at all while a terminal is up: the selected tab already carries that
|
||||
mark, in the strip, and two "you are here" marks is one too many.
|
||||
|
||||
The design pins a "Team vault" card to the foot of this list, beside a Team entry in the list itself.
|
||||
Two routes to one screen, one of them carrying a seat count nothing here can produce, so what sits at
|
||||
the foot is Preferences — which is where it already was, and which is the one entry that is about the
|
||||
machine rather than about the keychain.
|
||||
-->
|
||||
|
||||
<Border Width="54" Background="{StaticResource Chrome}"
|
||||
<Border Width="190" Background="{StaticResource Sidebar}"
|
||||
BorderBrush="{StaticResource Border}" BorderThickness="0,0,1,0">
|
||||
<DockPanel LastChildFill="False">
|
||||
|
||||
<StackPanel DockPanel.Dock="Top" Margin="0,8,0,0">
|
||||
<Button Classes="flat nav" Content="HOSTS" Classes.active="{Binding IsHostsShowing}"
|
||||
<StackPanel DockPanel.Dock="Top" Margin="8,10,8,0" Spacing="2">
|
||||
|
||||
<Button Classes="flat nav" Classes.active="{Binding IsHostsShowing}"
|
||||
Command="{Binding ShowScreenCommand}"
|
||||
CommandParameter="{x:Static vm:ShellScreen.Hosts}"
|
||||
ToolTip.Tip="Your hosts, and what is known about the one you have selected" />
|
||||
<Button Classes="flat nav" Content="FILES" Classes.active="{Binding IsTransfersShowing}"
|
||||
Command="{Binding ShowScreenCommand}"
|
||||
CommandParameter="{x:Static vm:ShellScreen.Transfers}"
|
||||
ToolTip.Tip="Move files to and from a host over SFTP" />
|
||||
<Button Classes="flat nav" Content="KEYS" Classes.active="{Binding IsVaultShowing}"
|
||||
ToolTip.Tip="Your hosts, and what is known about the one you have selected">
|
||||
<Grid ColumnDefinitions="Auto,*,Auto">
|
||||
<TextBlock Grid.Column="0" Classes="navicon" Text="▤" />
|
||||
<TextBlock Grid.Column="1" Classes="navlabel" Text="Hosts" />
|
||||
<TextBlock Grid.Column="2" Classes="navcount" Text="{Binding Vault.Hosts.Count}" />
|
||||
</Grid>
|
||||
</Button>
|
||||
|
||||
<Button Classes="flat nav" Classes.active="{Binding IsTransfersShowing}"
|
||||
Command="{Binding ShowFilesCommand}"
|
||||
CommandParameter="{x:Static vm:RemoteKind.Host}"
|
||||
ToolTip.Tip="Move files to and from a host over SFTP">
|
||||
<Grid ColumnDefinitions="Auto,*,Auto">
|
||||
<TextBlock Grid.Column="0" Classes="navicon" Text="⇅" />
|
||||
<TextBlock Grid.Column="1" Classes="navlabel" Text="SFTP" />
|
||||
</Grid>
|
||||
</Button>
|
||||
|
||||
<!--
|
||||
Buckets are their own destination now, as they are on the phone, rather than a toggle inside the
|
||||
files screen. Same screen behind both — an object store and an SFTP host are both an
|
||||
IRemoteFileStore — and the entry chosen is what decides which picker is offered. See ShowFiles.
|
||||
-->
|
||||
<Button Classes="flat nav" Classes.active="{Binding IsBucketsShowing}"
|
||||
Command="{Binding ShowFilesCommand}"
|
||||
CommandParameter="{x:Static vm:RemoteKind.Bucket}"
|
||||
ToolTip.Tip="Objects in an S3-compatible bucket from your keychain">
|
||||
<Grid ColumnDefinitions="Auto,*,Auto">
|
||||
<TextBlock Grid.Column="0" Classes="navicon" Text="◳" />
|
||||
<TextBlock Grid.Column="1" Classes="navlabel" Text="S3" />
|
||||
<TextBlock Grid.Column="2" Classes="navcount" Text="{Binding Vault.ObjectStores.Count}" />
|
||||
</Grid>
|
||||
</Button>
|
||||
|
||||
<!--
|
||||
TotalItemCount is keys plus passwords and deliberately excludes buckets, which used to disagree
|
||||
with the list under it. It no longer does: buckets have their own entry above, so this number and
|
||||
this screen now count the same things.
|
||||
-->
|
||||
<Button Classes="flat nav" Classes.active="{Binding IsVaultShowing}"
|
||||
Command="{Binding ShowScreenCommand}"
|
||||
CommandParameter="{x:Static vm:ShellScreen.Vault}"
|
||||
ToolTip.Tip="Your keychain: SSH keys, stored passwords, and the host keys you have approved" />
|
||||
<!--
|
||||
PINS, not HOST KEYS. The rail is 54 pixels wide at mono FontSize 9, which is five characters —
|
||||
and "pins" is what this codebase calls them everywhere else anyway.
|
||||
-->
|
||||
<Button Classes="flat nav" Content="PINS" Classes.active="{Binding IsKnownHostsShowing}"
|
||||
ToolTip.Tip="Your keychain: SSH keys and stored passwords">
|
||||
<Grid ColumnDefinitions="Auto,*,Auto">
|
||||
<TextBlock Grid.Column="0" Classes="navicon" Text="⚿" />
|
||||
<TextBlock Grid.Column="1" Classes="navlabel" Text="Keychain" />
|
||||
<TextBlock Grid.Column="2" Classes="navcount" Text="{Binding Vault.TotalItemCount}" />
|
||||
</Grid>
|
||||
</Button>
|
||||
|
||||
<Button Classes="flat nav" Classes.active="{Binding IsKnownHostsShowing}"
|
||||
Command="{Binding ShowScreenCommand}"
|
||||
CommandParameter="{x:Static vm:ShellScreen.KnownHosts}"
|
||||
ToolTip.Tip="Host keys you have approved, and how to withdraw one" />
|
||||
<!-- SNIPS, for the same five-character reason as PINS above. -->
|
||||
<Button Classes="flat nav" Content="SNIPS" Classes.active="{Binding IsSnippetsShowing}"
|
||||
ToolTip.Tip="Host keys you have approved, and how to withdraw one">
|
||||
<Grid ColumnDefinitions="Auto,*,Auto">
|
||||
<TextBlock Grid.Column="0" Classes="navicon" Text="◈" />
|
||||
<TextBlock Grid.Column="1" Classes="navlabel" Text="Pins" />
|
||||
<!--
|
||||
The vault's pins, not the screen's VisiblePins — that one is the filtered list, so a sidebar
|
||||
bound to it would count what the Pins screen's own filter box happens to match and would
|
||||
change as somebody typed in it. Every other count here is a total; this one has to be too.
|
||||
-->
|
||||
<TextBlock Grid.Column="2" Classes="navcount"
|
||||
Text="{Binding Vault.KnownHostPins.Count}" />
|
||||
</Grid>
|
||||
</Button>
|
||||
|
||||
<Button Classes="flat nav" Classes.active="{Binding IsSnippetsShowing}"
|
||||
Command="{Binding ShowScreenCommand}"
|
||||
CommandParameter="{x:Static vm:ShellScreen.Snippets}"
|
||||
ToolTip.Tip="Commands you have saved, and how to put one into a terminal" />
|
||||
<!-- LOGS, four characters, so it needs no abbreviating at all. -->
|
||||
<Button Classes="flat nav" Content="LOGS" Classes.active="{Binding IsLogsShowing}"
|
||||
ToolTip.Tip="Commands you have saved, and how to put one into a terminal">
|
||||
<Grid ColumnDefinitions="Auto,*,Auto">
|
||||
<TextBlock Grid.Column="0" Classes="navicon" Text="❯" />
|
||||
<TextBlock Grid.Column="1" Classes="navlabel" Text="Snippets" />
|
||||
<TextBlock Grid.Column="2" Classes="navcount" Text="{Binding Vault.Snippets.Count}" />
|
||||
</Grid>
|
||||
</Button>
|
||||
|
||||
<Button Classes="flat nav" Classes.active="{Binding IsLogsShowing}"
|
||||
Command="{Binding ShowScreenCommand}"
|
||||
CommandParameter="{x:Static vm:ShellScreen.Logs}"
|
||||
ToolTip.Tip="What has been connected to, and what has been changed in this keychain" />
|
||||
<Button Classes="flat nav" Content="TEAM" Classes.active="{Binding IsTeamShowing}"
|
||||
ToolTip.Tip="What has been connected to, and what has been changed in this keychain">
|
||||
<Grid ColumnDefinitions="Auto,*,Auto">
|
||||
<TextBlock Grid.Column="0" Classes="navicon" Text="≡" />
|
||||
<TextBlock Grid.Column="1" Classes="navlabel" Text="Logs" />
|
||||
</Grid>
|
||||
</Button>
|
||||
|
||||
<Button Classes="flat nav" Classes.active="{Binding IsTeamShowing}"
|
||||
Command="{Binding ShowScreenCommand}"
|
||||
CommandParameter="{x:Static vm:ShellScreen.Team}"
|
||||
ToolTip.Tip="Shared keychains and the people in them. Not built yet — see the screen for what is missing." />
|
||||
ToolTip.Tip="Shared keychains and the people in them">
|
||||
<Grid ColumnDefinitions="Auto,*,Auto">
|
||||
<TextBlock Grid.Column="0" Classes="navicon" Text="◎" />
|
||||
<!--
|
||||
No count. Teams are read from the server when the screen is opened, not on unlock, so this
|
||||
would read 0 until somebody had already been there — which is the one number on this list
|
||||
that would be a statement rather than a blank.
|
||||
-->
|
||||
<TextBlock Grid.Column="1" Classes="navlabel" Text="Teams" />
|
||||
</Grid>
|
||||
</Button>
|
||||
|
||||
</StackPanel>
|
||||
|
||||
<Button DockPanel.Dock="Bottom" Classes="flat nav" Content="PREFS"
|
||||
<Button DockPanel.Dock="Bottom" Classes="flat nav" Margin="8,0,8,10"
|
||||
Classes.active="{Binding IsPreferencesShowing}"
|
||||
Command="{Binding ShowScreenCommand}"
|
||||
CommandParameter="{x:Static vm:ShellScreen.Preferences}"
|
||||
ToolTip.Tip="Preferences, and this machine's device key" />
|
||||
ToolTip.Tip="Preferences, and this machine's device key">
|
||||
<Grid ColumnDefinitions="Auto,*,Auto">
|
||||
<TextBlock Grid.Column="0" Classes="navicon" Text="⚙" />
|
||||
<TextBlock Grid.Column="1" Classes="navlabel" Text="Preferences" />
|
||||
</Grid>
|
||||
</Button>
|
||||
|
||||
</DockPanel>
|
||||
</Border>
|
||||
|
||||
Reference in New Issue
Block a user