Give hosts and terminals their own screen, and the rest of the vault another
ci / build and test (ubuntu) (pull_request) Canceled after 0s
ci / build (windows) (pull_request) Canceled after 0s

Rebuilds the client's shell from an imported design: a titlebar and nav rail
it draws itself, real multi-session tabs over the one WebView, a Ctrl+K host
search, and a vault screen that merges keys, passwords and pinned host keys
into one table. Hosts left the vault column for their own screen beside the
terminal, which is what the design asks for and turned out to be the better
split anyway.

Two screens the design shows have nothing behind them yet — file transfer
and teams — and say so plainly rather than rendering invented data; every
other gap between the design and this build is recorded in
docs/design-import-gaps.md.
This commit is contained in:
jaap-jan committed 2026-07-31 08:39:37 +02:00
1 parent d162271a45
commit 9a76eced14
37 files changed
+4672 -1347

No files matched your search

@@ -0,0 +1,90 @@
using Avalonia;
using Avalonia.Controls;
namespace DodoSSH.Client.App.Views;
/// <summary>
/// The screen behind a nav-rail entry whose feature does not exist yet.
/// </summary>
/// <remarks>
/// <para>
/// Styled properties set from XAML rather than a view model, because there is no state here — a screen
/// saying what is absent has nothing to observe, and a view model per absent feature would be two classes
/// whose only job is to hold four strings.
/// </para>
/// <para>
/// <see cref="Missing"/> takes the list rather than the markup listing it inline, so the two instances of
/// this control differ only in their content and the layout is written once.
/// </para>
/// </remarks>
internal sealed partial class NotBuiltScreen : UserControl
{
/// <summary>What the feature is called, in the design's own words.</summary>
public static readonly StyledProperty<string> TitleProperty =
AvaloniaProperty.Register<NotBuiltScreen, string>(nameof(Title), string.Empty);
/// <summary>Which milestone it belongs to, so the chip is a fact and not an apology.</summary>
public static readonly StyledProperty<string> MilestoneProperty =
AvaloniaProperty.Register<NotBuiltScreen, string>(nameof(Milestone), string.Empty);
/// <summary>One paragraph on what the design shows and why none of it is here.</summary>
public static readonly StyledProperty<string> SummaryProperty =
AvaloniaProperty.Register<NotBuiltScreen, string>(nameof(Summary), string.Empty);
/// <summary>The specific pieces, each naming the layer it would land in.</summary>
public static readonly StyledProperty<IEnumerable<string>?> MissingProperty =
AvaloniaProperty.Register<NotBuiltScreen, IEnumerable<string>?>(nameof(Missing));
/// <summary>What somebody wanting this today should do instead.</summary>
public static readonly StyledProperty<string> InsteadProperty =
AvaloniaProperty.Register<NotBuiltScreen, string>(nameof(Instead), string.Empty);
public NotBuiltScreen()
{
InitializeComponent();
// Bound in code rather than in the markup, because the markup's own DataContext is the shell and
// these five are properties of this control. Self-bindings in XAML would need a relative source on
// every one of them, which is five chances to point one at the wrong object.
TitleText.Bind(TextBlock.TextProperty, this.GetObservable(TitleProperty));
MilestoneText.Bind(TextBlock.TextProperty, this.GetObservable(MilestoneProperty));
SummaryText.Bind(TextBlock.TextProperty, this.GetObservable(SummaryProperty));
InsteadText.Bind(TextBlock.TextProperty, this.GetObservable(InsteadProperty));
MissingList.Bind(ItemsControl.ItemsSourceProperty, this.GetObservable(MissingProperty));
}
/// <inheritdoc cref="TitleProperty" />
public string Title
{
get => GetValue(TitleProperty);
set => SetValue(TitleProperty, value);
}
/// <inheritdoc cref="MilestoneProperty" />
public string Milestone
{
get => GetValue(MilestoneProperty);
set => SetValue(MilestoneProperty, value);
}
/// <inheritdoc cref="SummaryProperty" />
public string Summary
{
get => GetValue(SummaryProperty);
set => SetValue(SummaryProperty, value);
}
/// <inheritdoc cref="MissingProperty" />
public IEnumerable<string>? Missing
{
get => GetValue(MissingProperty);
set => SetValue(MissingProperty, value);
}
/// <inheritdoc cref="InsteadProperty" />
public string Instead
{
get => GetValue(InsteadProperty);
set => SetValue(InsteadProperty, value);
}
}