Restyle the keychain, the snips and the logs to their v5b shapes

This commit is contained in:
2026-08-08 00:49:59 +02:00
parent 43c939b697
commit d91729c0b8
3 changed files with 1117 additions and 794 deletions
File diff suppressed because it is too large Load Diff
+219 -118
View File
@@ -18,136 +18,237 @@
The connections list shows anything still open at the top, marked "still open" rather than with a dash. A The connections list shows anything still open at the top, marked "still open" rather than with a dash. A
dash would read as a missing recording, and the two are opposite facts — an entry is written once, when a dash would read as a missing recording, and the two are opposite facts — an entry is written once, when a
connection closes, so a live session is deliberately not in the vault yet. connection closes, so a live session is deliberately not in the vault yet.
── v5b — Logs.dc.html ──────────────────────────────────────────────────────────────────────────────────
A fidelity pass, not a new shape. What moved:
◆ THE HEADER. "Logs" 33 bold plus the two-segment CONNECTIONS/KEYCHAIN control, restyled onto
Border.navtrack/Button.navseg — the same h31 track the rail's own SSH/SFTP/S3 switcher already uses,
reused rather than redrawn, since it is the identical shape at the identical size. Two segments and not
three: LOGS is the screen's own title, not a tab, so the design's own two — CONNECTIONS and KEYCHAIN —
are the whole of what this screen switches between, exactly matching LogSection's own two values. No
deviation to record here. On the right: a mono status sentence — LogsViewModel.HeaderStatusLine, which
shows a refresh error when there is one and otherwise the fact this screen's own header comment already
states about whichever log is showing — and REFRESH, unchanged.
◆ THE BODY. A bordered radius-12 container on Pane bg wraps each table now, in place of the plain
background either one drew before. Tracked column headers are unchanged — HOST/ADDRESS/LASTED/KIND/
STARTED/FROM for connections, ITEM/TYPE/WHAT/FIELDS/WHEN for keychain activity, both already matching the
design exactly. Rows keep their real two-state host dot (green only for a session genuinely open right
now, per HasVisible on ConnectionLogRowViewModel.IsLive — never a third colour) and their existing
"failed"/"host key refused" amber badges. WHAT, on the activity table, is now a small coloured chip rather
than plain text — green for created, purple for a plain change, red for deleted — reading the same
ActivityOperation this screen already resolved to a word; a chip rather than a fabricated new fact.
◆ THE FOOTER. Logs.dc.html's own lock-glyph sentence — "Both logs are ordinary synced keychain items —
end-to-end encrypted. The server learns only that rows exist and when they were written." — verified
against this file's own header remark above and ADR 0001 before shipping it: both are true, so the
sentence is drawn as literal text rather than reworded.
--> -->
<Grid RowDefinitions="Auto,*"> <UserControl.Styles>
<Style Selector="Border.logtable">
<Setter Property="BorderBrush" Value="{StaticResource Border}" />
<Setter Property="BorderThickness" Value="1" />
<Setter Property="CornerRadius" Value="12" />
<Setter Property="Background" Value="{StaticResource Pane}" />
<Setter Property="ClipToBounds" Value="True" />
</Style>
<Style Selector="ListBox.logrows > ListBoxItem /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="CornerRadius" Value="8" />
</Style>
<Style Selector="ListBox.logrows > ListBoxItem:pointerover /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="{StaticResource Track}" />
</Style>
<!--
WHAT, on the activity table: created/changed/deleted, coloured the way the item badges are. "changed"
is the base rule's own colour — it is the default ActivityLogRowViewModel.Operation falls through to —
and .created/.deleted are declared after it so they win; Avalonia has no specificity and settles two
matching rules by declaration order, the same trap this application's other screens record.
-->
<Style Selector="Border.opchip">
<Setter Property="Background" Value="{StaticResource AccentWash}" />
</Style>
<Style Selector="Border.opchip > TextBlock">
<Setter Property="Foreground" Value="{StaticResource AccentText}" />
</Style>
<Style Selector="Border.opchip.created">
<Setter Property="Background" Value="{StaticResource LiveWash}" />
</Style>
<Style Selector="Border.opchip.created > TextBlock">
<Setter Property="Foreground" Value="{StaticResource Live}" />
</Style>
<Style Selector="Border.opchip.deleted">
<Setter Property="Background" Value="{StaticResource DangerWash}" />
</Style>
<Style Selector="Border.opchip.deleted > TextBlock">
<Setter Property="Foreground" Value="{StaticResource Danger}" />
</Style>
</UserControl.Styles>
<Border Grid.Row="0" Padding="14,0" Height="44" <Grid RowDefinitions="Auto,*" Margin="26">
BorderBrush="{StaticResource Border}" BorderThickness="0,0,0,1">
<Grid ColumnDefinitions="Auto,Auto,Auto,*,Auto" VerticalAlignment="Center">
<TextBlock Grid.Column="0" Classes="mono" Text="LOGS" FontSize="12" FontWeight="SemiBold"
LetterSpacing="1" Foreground="{StaticResource Text}" VerticalAlignment="Center"
Margin="0,0,14,0" />
<Button Grid.Column="1" Classes="flat cat" Content="CONNECTIONS" <!-- ============ THE HEADER ============ -->
Classes.active="{Binding ShowsConnections}" <Grid Grid.Row="0" Margin="0,0,0,20" ColumnDefinitions="Auto,Auto,*,Auto,Auto">
Command="{Binding ShowSectionCommand}"
CommandParameter="{x:Static vm:LogSection.Connections}" />
<Button Grid.Column="2" Classes="flat cat" Content="KEYCHAIN"
Classes.active="{Binding ShowsActivity}"
Command="{Binding ShowSectionCommand}"
CommandParameter="{x:Static vm:LogSection.Activity}" />
<TextBlock Grid.Column="3" Classes="mono" Text="{Binding Status}" FontSize="10.5" <TextBlock Grid.Column="0" Text="Logs" FontSize="33" FontWeight="Bold" LetterSpacing="-0.5"
Foreground="{StaticResource TextFaint}" Margin="14,0,0,0" Foreground="{StaticResource Text}" VerticalAlignment="Center" />
TextTrimming="CharacterEllipsis" VerticalAlignment="Center" />
<Border Grid.Column="1" Classes="navtrack" Margin="14,0,0,0" Width="200" VerticalAlignment="Center">
<Grid ColumnDefinitions="*,*">
<Button Grid.Column="0" Classes="navseg" Classes.active="{Binding ShowsConnections}"
Content="CONNECTIONS" Command="{Binding ShowSectionCommand}"
CommandParameter="{x:Static vm:LogSection.Connections}" />
<Button Grid.Column="1" Classes="navseg" Classes.active="{Binding ShowsActivity}"
Content="KEYCHAIN" Command="{Binding ShowSectionCommand}"
CommandParameter="{x:Static vm:LogSection.Activity}" />
</Grid>
</Border>
<TextBlock Grid.Column="3" Classes="mono" Text="{Binding HeaderStatusLine}" FontSize="10.5"
Foreground="{StaticResource TextFaint}" Margin="0,0,14,0"
TextTrimming="CharacterEllipsis" VerticalAlignment="Center" />
<Button Grid.Column="4" Classes="ghost" Height="40"
Command="{Binding RefreshCommand}" IsEnabled="{Binding !IsBusy}">
<StackPanel Orientation="Horizontal" Spacing="8">
<TextBlock FontFamily="{StaticResource IconFont}" FontSize="15" Text="&#xE5D5;"
Foreground="{StaticResource TextFaint}" />
<TextBlock Text="Refresh" FontSize="13.5" FontWeight="SemiBold"
Foreground="{StaticResource TextDim}" />
</StackPanel>
</Button>
</Grid>
<!-- ============ Connections ============ -->
<Border Grid.Row="1" Classes="logtable" IsVisible="{Binding ShowsConnections}">
<Grid RowDefinitions="Auto,*,Auto">
<Grid Grid.Row="0" ColumnDefinitions="1.2*,1.6*,88,72,90,*" Margin="20,14,20,10"
IsVisible="{Binding HasConnections}">
<TextBlock Grid.Column="0" Classes="label" Text="HOST" FontSize="9.5" LetterSpacing="1" />
<TextBlock Grid.Column="1" Classes="label" Text="ADDRESS" FontSize="9.5" LetterSpacing="1" />
<TextBlock Grid.Column="2" Classes="label" Text="LASTED" FontSize="9.5" LetterSpacing="1" />
<TextBlock Grid.Column="3" Classes="label" Text="KIND" FontSize="9.5" LetterSpacing="1" />
<TextBlock Grid.Column="4" Classes="label" Text="STARTED" FontSize="9.5" LetterSpacing="1" />
<TextBlock Grid.Column="5" Classes="label" Text="FROM" FontSize="9.5" LetterSpacing="1" />
</Grid>
<ListBox Grid.Row="1" x:Name="ConnectionList" Classes="logrows" Focusable="True"
ItemsSource="{Binding Connections}" Margin="12,0,12,10">
<ListBox.ItemTemplate>
<DataTemplate x:DataType="vm:ConnectionLogRowViewModel">
<Grid ColumnDefinitions="1.2*,1.6*,88,72,90,*" Height="40" Margin="8,0,10,0">
<StackPanel Grid.Column="0" Orientation="Horizontal" Spacing="9" Margin="12,0,8,0"
VerticalAlignment="Center">
<Ellipse Classes="dot" Classes.live="{Binding IsLive}" VerticalAlignment="Center" />
<TextBlock Classes="mono" Text="{Binding HostLabel}" FontSize="12.5" FontWeight="Medium"
Foreground="{StaticResource Text}" TextTrimming="CharacterEllipsis" />
</StackPanel>
<TextBlock Grid.Column="1" Classes="mono" Text="{Binding Address}" FontSize="11"
Foreground="{StaticResource TextGhost}" Margin="0,0,8,0"
TextTrimming="CharacterEllipsis" VerticalAlignment="Center" />
<TextBlock Grid.Column="2" Classes="mono" Text="{Binding Duration}" FontSize="11"
Foreground="{StaticResource TextGhost}" VerticalAlignment="Center" />
<TextBlock Grid.Column="3" Classes="mono" Text="{Binding Kind}" FontSize="10.5"
Foreground="{StaticResource TextFaint}" VerticalAlignment="Center" />
<TextBlock Grid.Column="4" Classes="mono" Text="{Binding Started}" FontSize="10.5"
Foreground="{StaticResource TextFaint}" VerticalAlignment="Center" />
<StackPanel Grid.Column="5" Orientation="Horizontal" Spacing="8" VerticalAlignment="Center">
<TextBlock Classes="mono" Text="{Binding DeviceName}" FontSize="10.5"
Foreground="{StaticResource TextFaint}"
TextTrimming="CharacterEllipsis" />
<!--
Only when there is something to say. A connection that opened and closed says nothing
here; one that was refused says so, and that is the row worth finding in a long list.
-->
<Border Classes="chip warn" Padding="7,0" Height="18" IsVisible="{Binding HasOutcome}">
<TextBlock Text="{Binding Outcome}" FontSize="9.5" />
</Border>
</StackPanel>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<TextBlock Grid.Row="1" Classes="hint" Text="{Binding EmptyMessage}" FontSize="12"
Margin="24" HorizontalAlignment="Center" VerticalAlignment="Center"
TextAlignment="Center" MaxWidth="420"
IsVisible="{Binding !HasConnections}" />
<!-- ◆ THE FOOTER. Verified against this file's own header remark and ADR 0001; see the file-level note. -->
<Border Grid.Row="2" BorderBrush="{StaticResource Border}" BorderThickness="0,1,0,0"
Padding="20,12">
<StackPanel Orientation="Horizontal" Spacing="10">
<TextBlock FontFamily="{StaticResource IconFont}" FontSize="13" Text="&#xE897;"
Foreground="{StaticResource TextFaint}" />
<TextBlock Classes="hint" FontSize="11" TextWrapping="Wrap"
Foreground="{StaticResource TextFaint}"
Text="Both logs are ordinary synced keychain items — end-to-end encrypted. The server learns only that rows exist and when they were written." />
</StackPanel>
</Border>
<Button Grid.Column="4" Classes="ghost" Content="REFRESH" Command="{Binding RefreshCommand}"
IsEnabled="{Binding !IsBusy}" />
</Grid> </Grid>
</Border> </Border>
<!-- ============ Connections ============ -->
<Grid Grid.Row="1" RowDefinitions="Auto,*" IsVisible="{Binding ShowsConnections}">
<Grid Grid.Row="0" ColumnDefinitions="1.2*,1.6*,88,72,90,*" Margin="14,6,14,6"
IsVisible="{Binding HasConnections}">
<TextBlock Grid.Column="0" Classes="label" Text="HOST" FontSize="9.5" LetterSpacing="1" />
<TextBlock Grid.Column="1" Classes="label" Text="ADDRESS" FontSize="9.5" LetterSpacing="1" />
<TextBlock Grid.Column="2" Classes="label" Text="LASTED" FontSize="9.5" LetterSpacing="1" />
<TextBlock Grid.Column="3" Classes="label" Text="KIND" FontSize="9.5" LetterSpacing="1" />
<TextBlock Grid.Column="4" Classes="label" Text="STARTED" FontSize="9.5" LetterSpacing="1" />
<TextBlock Grid.Column="5" Classes="label" Text="FROM" FontSize="9.5" LetterSpacing="1" />
</Grid>
<ListBox Grid.Row="1" x:Name="ConnectionList" Focusable="True"
ItemsSource="{Binding Connections}">
<ListBox.ItemTemplate>
<DataTemplate x:DataType="vm:ConnectionLogRowViewModel">
<Grid ColumnDefinitions="1.2*,1.6*,88,72,90,*" Margin="0,6,14,6">
<StackPanel Grid.Column="0" Orientation="Horizontal" Spacing="6" Margin="14,0,8,0">
<Ellipse Classes="dot" Classes.live="{Binding IsLive}" VerticalAlignment="Center" />
<TextBlock Classes="mono" Text="{Binding HostLabel}" FontSize="12" FontWeight="Medium"
Foreground="{StaticResource Text}" TextTrimming="CharacterEllipsis"
VerticalAlignment="Center" />
</StackPanel>
<TextBlock Grid.Column="1" Classes="mono" Text="{Binding Address}" FontSize="10.5"
Foreground="{StaticResource TextDim}" Margin="0,0,8,0"
TextTrimming="CharacterEllipsis" VerticalAlignment="Center" />
<StackPanel Grid.Column="2" Orientation="Horizontal" Spacing="6" VerticalAlignment="Center">
<TextBlock Classes="mono" Text="{Binding Duration}" FontSize="10.5"
Foreground="{StaticResource TextDim}" />
</StackPanel>
<TextBlock Grid.Column="3" Classes="mono" Text="{Binding Kind}" FontSize="10"
Foreground="{StaticResource TextFaint}" VerticalAlignment="Center" />
<TextBlock Grid.Column="4" Classes="mono" Text="{Binding Started}" FontSize="10"
Foreground="{StaticResource TextFaint}" VerticalAlignment="Center" />
<StackPanel Grid.Column="5" Orientation="Horizontal" Spacing="6" VerticalAlignment="Center">
<TextBlock Classes="mono" Text="{Binding DeviceName}" FontSize="10"
Foreground="{StaticResource TextFaint}"
TextTrimming="CharacterEllipsis" />
<!--
Only when there is something to say. A connection that opened and closed says nothing
here; one that was refused says so, and that is the row worth finding in a long list.
-->
<Border Classes="chip warn" Padding="4,0" IsVisible="{Binding HasOutcome}">
<TextBlock Text="{Binding Outcome}" FontSize="9.5" />
</Border>
</StackPanel>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<TextBlock Grid.Row="1" Classes="hint" Text="{Binding EmptyMessage}" FontSize="12"
Margin="24" HorizontalAlignment="Center" VerticalAlignment="Center"
TextAlignment="Center" MaxWidth="420"
IsVisible="{Binding !HasConnections}" />
</Grid>
<!-- ============ Keychain changes ============ --> <!-- ============ Keychain changes ============ -->
<Grid Grid.Row="1" RowDefinitions="Auto,*" IsVisible="{Binding ShowsActivity}"> <Border Grid.Row="1" Classes="logtable" IsVisible="{Binding ShowsActivity}">
<Grid RowDefinitions="Auto,*,Auto">
<Grid Grid.Row="0" ColumnDefinitions="1.4*,0.8*,0.8*,1.6*,0.8*" Margin="20,14,20,10"
IsVisible="{Binding HasActivity}">
<TextBlock Grid.Column="0" Classes="label" Text="ITEM" FontSize="9.5" LetterSpacing="1" />
<TextBlock Grid.Column="1" Classes="label" Text="TYPE" FontSize="9.5" LetterSpacing="1" />
<TextBlock Grid.Column="2" Classes="label" Text="WHAT" FontSize="9.5" LetterSpacing="1" />
<TextBlock Grid.Column="3" Classes="label" Text="FIELDS" FontSize="9.5" LetterSpacing="1" />
<TextBlock Grid.Column="4" Classes="label" Text="WHEN" FontSize="9.5" LetterSpacing="1" />
</Grid>
<ListBox Grid.Row="1" x:Name="ActivityList" Classes="logrows" Focusable="True"
ItemsSource="{Binding Activity}" Margin="12,0,12,10">
<ListBox.ItemTemplate>
<DataTemplate x:DataType="vm:ActivityLogRowViewModel">
<Grid ColumnDefinitions="1.4*,0.8*,0.8*,1.6*,0.8*" Height="40" Margin="8,0,10,0">
<TextBlock Grid.Column="0" Classes="mono" Text="{Binding ItemLabel}" FontSize="12.5"
FontWeight="Medium" Foreground="{StaticResource Text}" Margin="4,0,8,0"
TextTrimming="CharacterEllipsis" VerticalAlignment="Center" />
<TextBlock Grid.Column="1" Classes="mono" Text="{Binding ItemKind}" FontSize="10.5"
Foreground="{StaticResource TextGhost}" VerticalAlignment="Center" />
<Border Grid.Column="2" Classes="opchip" Classes.created="{Binding IsCreated}"
Classes.deleted="{Binding IsDeleted}" Height="19" CornerRadius="5" Padding="8,0"
HorizontalAlignment="Left" VerticalAlignment="Center">
<TextBlock Text="{Binding Operation}" FontSize="10" FontWeight="SemiBold" />
</Border>
<TextBlock Grid.Column="3" Classes="mono" Text="{Binding ChangedFields}" FontSize="10.5"
Foreground="{StaticResource TextFaint}" Margin="0,0,8,0"
TextTrimming="CharacterEllipsis" VerticalAlignment="Center"
IsVisible="{Binding HasChangedFields}" />
<TextBlock Grid.Column="4" Classes="mono" Text="{Binding At}" FontSize="10.5"
Foreground="{StaticResource TextFaint}" VerticalAlignment="Center" />
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<TextBlock Grid.Row="1" Classes="hint" Text="{Binding EmptyMessage}" FontSize="12"
Margin="24" HorizontalAlignment="Center" VerticalAlignment="Center"
TextAlignment="Center" MaxWidth="420"
IsVisible="{Binding !HasActivity}" />
<Border Grid.Row="2" BorderBrush="{StaticResource Border}" BorderThickness="0,1,0,0"
Padding="20,12">
<StackPanel Orientation="Horizontal" Spacing="10">
<TextBlock FontFamily="{StaticResource IconFont}" FontSize="13" Text="&#xE897;"
Foreground="{StaticResource TextFaint}" />
<TextBlock Classes="hint" FontSize="11" TextWrapping="Wrap"
Foreground="{StaticResource TextFaint}"
Text="Both logs are ordinary synced keychain items — end-to-end encrypted. The server learns only that rows exist and when they were written." />
</StackPanel>
</Border>
<Grid Grid.Row="0" ColumnDefinitions="1.2*,90,96,*,90" Margin="14,6,14,6"
IsVisible="{Binding HasActivity}">
<TextBlock Grid.Column="0" Classes="label" Text="ITEM" FontSize="9.5" LetterSpacing="1" />
<TextBlock Grid.Column="1" Classes="label" Text="TYPE" FontSize="9.5" LetterSpacing="1" />
<TextBlock Grid.Column="2" Classes="label" Text="WHAT" FontSize="9.5" LetterSpacing="1" />
<TextBlock Grid.Column="3" Classes="label" Text="FIELDS" FontSize="9.5" LetterSpacing="1" />
<TextBlock Grid.Column="4" Classes="label" Text="WHEN" FontSize="9.5" LetterSpacing="1" />
</Grid> </Grid>
</Border>
<ListBox Grid.Row="1" x:Name="ActivityList" Focusable="True" ItemsSource="{Binding Activity}">
<ListBox.ItemTemplate>
<DataTemplate x:DataType="vm:ActivityLogRowViewModel">
<Grid ColumnDefinitions="1.2*,90,96,*,90" Margin="14,6,14,6">
<TextBlock Grid.Column="0" Classes="mono" Text="{Binding ItemLabel}" FontSize="12"
FontWeight="Medium" Foreground="{StaticResource Text}" Margin="0,0,8,0"
TextTrimming="CharacterEllipsis" VerticalAlignment="Center" />
<TextBlock Grid.Column="1" Classes="mono" Text="{Binding ItemKind}" FontSize="10"
Foreground="{StaticResource TextFaint}" VerticalAlignment="Center" />
<TextBlock Grid.Column="2" Classes="mono" Text="{Binding Operation}" FontSize="10.5"
Foreground="{StaticResource TextDim}" VerticalAlignment="Center" />
<!--
The names of the fields that changed, and never what they changed to. A log that recorded
an old password would be a plaintext credential store with a vault drawn around it.
-->
<TextBlock Grid.Column="3" Classes="mono" Text="{Binding ChangedFields}" FontSize="10.5"
Foreground="{StaticResource TextFaint}" Margin="0,0,8,0"
TextTrimming="CharacterEllipsis" VerticalAlignment="Center"
IsVisible="{Binding HasChangedFields}" />
<TextBlock Grid.Column="4" Classes="mono" Text="{Binding At}" FontSize="10"
Foreground="{StaticResource TextFaint}" VerticalAlignment="Center" />
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<TextBlock Grid.Row="1" Classes="hint" Text="{Binding EmptyMessage}" FontSize="12"
Margin="24" HorizontalAlignment="Center" VerticalAlignment="Center"
TextAlignment="Center" MaxWidth="420"
IsVisible="{Binding !HasActivity}" />
</Grid>
</Grid> </Grid>
+327 -215
View File
@@ -10,245 +10,357 @@
The list and the writing belong to the vault, as every other item kind's do; this screen is the filter, The list and the writing belong to the vault, as every other item kind's do; this screen is the filter,
the editor and the insert over the top. See SnippetsViewModel. the editor and the insert over the top. See SnippetsViewModel.
The two buttons at the bottom right are the whole safety design, and their wording is load-bearing. The two buttons at the bottom right of the detail pane are the whole safety design, and their wording is
A terminal is one input stream with no notion of being at a prompt — the remote may be inside vi, or at load-bearing. A terminal is one input stream with no notion of being at a prompt — the remote may be
a sudo password prompt with echo off — so this application cannot say "run this command", only "type inside vi, or at a sudo password prompt with echo off — so this application cannot say "run this
this into whatever is there". RUN appears solely for a snippet whose own flag says it runs, which makes command", only "type this into whatever is there". RUN appears solely for a snippet whose own flag says
that a decision taken once while writing it rather than a button beside every one of them. it runs, which makes that a decision taken once while writing it rather than a button beside every one
of them.
── v5b — Snippets.dc.html ──────────────────────────────────────────────────────────────────────────────
A fidelity pass, not a new shape. What moved:
◆ THE SCREEN'S OWN TITLE. The design renames this screen "Snips" — adopted here on screen, matching the
nav rail, which already says Snips (see NavRail.axaml's own remark on the mock's wording for this
destination).
◆ THE HEADER. "Snips" 33 bold, a count chip, the existing filter box restyled to 280px, and a
"+ New snip" accent button in place of the old ghost "+ NEW SNIPPET" — same NewCommand.
◆ THE LIST. Card rows rather than a flat list — radius 10, Track fill and an accent ring on the selected
one, Track on hover — with a { } glyph, the mono name, the "runs immediately" chip (real: it is
RunsOnInsert, already tracked per snippet), the vault chip, and the mono command preview beneath. No
modified date: SnippetSecret carries none, and neither does any other item kind — VaultItem is (id,
secret, version, three sync flags) and nothing else; see docs/design-import-gaps.md. The bottom action
row keeps EDIT / MOVE TO VAULT… / DELETE, restyled and DELETE now danger-coloured to match.
◆ THE DETAIL/EDITOR SIDEBAR, widened to 340px per the design. Two modes exist as they always have — the
editor while IsEditing, the selected snippet's own facts and its insert controls otherwise — because the
two are genuinely different forms doing different things, not a cosmetic split; merging them would let
somebody fixing a typo re-file a snippet by leaving a vault picker where they found it. VAULT draws a
lock glyph beside the vault name, per the design. "Runs on insert"/"runs immediately" keeps this screen's
own longer caption rather than the design's shorter one — it states the operational consequence
("typed at the prompt and waits for you") where the design's states only the rationale for having the
setting once; both are true, the existing one is more actionable. The insert button keeps its own dynamic
label — "TYPE INTO {tab}" or "NO TERMINAL OPEN" — over the design's static "Type into terminal", because
naming the destination tab is a fact the design's caption does not carry and this screen already had.
Its own caption similarly stays: it names vi and a sudo prompt with echo off as the two ways "at a
prompt" can be wrong, which is more than the design's own sentence says.
◆ THE DELETE CONFIRMATION. Snippets.dc.html's own modal — vault-wide reach, a tombstone, no undo — is
exactly what VaultViewModel.HowFarADeletionGoes already says for every other kind of item, so
SnippetsViewModel.RequestDelete/ConfirmDelete/CancelDelete say it in the same words. Additive next to the
existing uncounted DeleteCommand rather than a change to it — that command is what the phone's own DELETE
row still calls, with no confirmation card on that screen to answer one; see SnippetsViewModel's own
remarks on both commands.
--> -->
<Grid ColumnDefinitions="*,300"> <UserControl.Styles>
<!--
A card, not a full-bleed row: Track fill on hover, Track fill plus an accent ring on the selected one,
matching the design's own "selected = Track bg + accent ring, hover Track" — distinct from
ListBox.filerows, which draws no ring, because a snippet card is chosen for one editor at a time rather
than opened directly.
-->
<Style Selector="ListBox.snipcards > ListBoxItem /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="CornerRadius" Value="10" />
<Setter Property="BorderThickness" Value="1" />
<Setter Property="BorderBrush" Value="Transparent" />
</Style>
<Style Selector="ListBox.snipcards > ListBoxItem:pointerover /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="{StaticResource Track}" />
</Style>
<Style Selector="ListBox.snipcards > ListBoxItem:selected /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="{StaticResource Track}" />
<Setter Property="BorderBrush" Value="{StaticResource Accent}" />
</Style>
<Style Selector="ListBox.snipcards > ListBoxItem:selected:pointerover /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="{StaticResource Track}" />
<Setter Property="BorderBrush" Value="{StaticResource Accent}" />
</Style>
</UserControl.Styles>
<Grid Grid.Column="0" RowDefinitions="Auto,*,Auto"> <Grid RowDefinitions="Auto,*" Margin="26">
<Border Grid.Row="0" Padding="14,0" Height="44" <!-- ============ THE HEADER ============ -->
BorderBrush="{StaticResource Border}" BorderThickness="0,0,0,1"> <Grid Grid.Row="0" Margin="0,0,0,20" ColumnDefinitions="Auto,Auto,*,Auto,Auto">
<Grid ColumnDefinitions="Auto,*,Auto" VerticalAlignment="Center">
<TextBlock Grid.Column="0" Classes="mono" Text="SNIPPETS" FontSize="12"
FontWeight="SemiBold" LetterSpacing="1" Foreground="{StaticResource Text}"
VerticalAlignment="Center" />
<TextBlock Grid.Column="1" Classes="mono" Text="{Binding Status}" FontSize="10.5"
Foreground="{StaticResource TextFaint}" Margin="10,0,0,0"
TextTrimming="CharacterEllipsis" VerticalAlignment="Center" />
<!-- <TextBlock Grid.Column="0" Text="Snips" FontSize="33" FontWeight="Bold" LetterSpacing="-0.5"
The command is searched as well as the name: half of what anybody remembers about a saved Foreground="{StaticResource Text}" VerticalAlignment="Center" />
command is a word that was inside it.
--> <Border Grid.Column="1" Margin="12,0,0,0" MinWidth="34" Height="30" CornerRadius="9" Padding="8,0"
<TextBox Grid.Column="2" x:Name="SnippetFilter" Text="{Binding Filter}" Width="240" Background="{StaticResource Chip}" VerticalAlignment="Center">
PlaceholderText="filter by name or command" VerticalAlignment="Center" /> <TextBlock Classes="mono" Text="{Binding Visible.Count}" FontSize="12.5"
</Grid> Foreground="{StaticResource TextDim}" HorizontalAlignment="Center"
VerticalAlignment="Center" />
</Border> </Border>
<ListBox Grid.Row="1" x:Name="SnippetList" Focusable="True" <!--
ItemsSource="{Binding Visible}" The command is searched as well as the name: half of what anybody remembers about a saved command is
SelectedItem="{Binding Selected}"> a word that was inside it.
<ListBox.ItemTemplate> -->
<DataTemplate x:DataType="vm:SnippetRowViewModel"> <TextBox Grid.Column="3" x:Name="SnippetFilter" Text="{Binding Filter}" Width="280" Height="40"
<Grid ColumnDefinitions="2,*" Margin="0,7,14,7"> CornerRadius="10" Margin="0,0,10,0"
<Border Grid.Column="0" Classes="rowmark" /> PlaceholderText="filter by name or command" VerticalAlignment="Center" />
<StackPanel Grid.Column="1" Margin="12,0,0,0" Spacing="2">
<StackPanel Orientation="Horizontal" Spacing="6">
<TextBlock Classes="mono" Text="{Binding Label}" FontSize="12" FontWeight="Medium"
Foreground="{StaticResource Text}" TextTrimming="CharacterEllipsis" />
<!--
The flag, where the decision is made. A snippet that presses Enter for you is not the
same kind of thing as one that does not, and the list is where somebody chooses between
them.
-->
<Border Classes="chip warn" Padding="4,0" IsVisible="{Binding RunsOnInsert}">
<TextBlock Text="runs immediately" FontSize="9.5" />
</Border>
<Border Classes="chip warn" Padding="4,0"
IsVisible="{Binding Badge, Converter={x:Static StringConverters.IsNotNullOrEmpty}}">
<TextBlock Text="{Binding Badge}" FontSize="9.5" />
</Border>
<!--
Which vault this one is in, drawn only where there is more than one to be in. A shared
snippet is a command the rest of a team can read and insert; the row is where somebody
decides whether the thing they are about to edit is theirs alone.
-->
<Border Classes="chip" Padding="4,0" IsVisible="{Binding HasVaultBadge}">
<TextBlock Text="{Binding VaultBadge}" FontSize="9.5" />
</Border>
</StackPanel>
<!--
Newlines shown as ⏎ rather than dropped. A three-line snippet flattened into one run of
text reads as a single command, which is the thing being decided about on this row.
-->
<TextBlock Classes="mono" Text="{Binding Preview}" FontSize="10.5"
Foreground="{StaticResource TextFaint}" TextTrimming="CharacterEllipsis" />
</StackPanel>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<TextBlock Grid.Row="1" Classes="hint" Text="{Binding EmptyMessage}" FontSize="12" <Button Grid.Column="4" Classes="accent" Height="40" FontSize="13.5" Content="+ New snip"
Margin="24" HorizontalAlignment="Center" VerticalAlignment="Center" Command="{Binding NewCommand}" />
TextAlignment="Center" MaxWidth="360"
IsVisible="{Binding !HasVisible}" />
<Border Grid.Row="2" Padding="14,8" BorderBrush="{StaticResource BorderSubtle}"
BorderThickness="0,1,0,0">
<StackPanel Orientation="Horizontal" Spacing="6">
<Button Classes="ghost" Content="+ NEW SNIPPET" Command="{Binding NewCommand}" />
<Button Classes="ghost" Content="EDIT" Command="{Binding EditCommand}"
IsEnabled="{Binding HasSelection}" />
<Button Classes="ghost" Content="DELETE" Command="{Binding DeleteCommand}"
IsEnabled="{Binding HasSelection}" />
<!--
Sharing a snippet, which is what moving one into a team's vault is. Beside EDIT rather than
inside it: the two vaults are encrypted under different keys, so this is a re-seal into one and
a tombstone in the other — nothing a SAVE could do — and a picker inside the form would let
somebody correcting a typo hand a command to a team by leaving it where they found it. The
picker itself opens beside the snippet, in the pane on the right.
-->
<Button Classes="ghost" Content="MOVE TO VAULT…" Command="{Binding MoveCommand}"
IsEnabled="{Binding CanMove}"
ToolTip.Tip="Re-encrypts this snippet with another vault's key. Everybody who holds that key can then read and insert it." />
</StackPanel>
</Border>
</Grid> </Grid>
<Border Grid.Column="1" Background="{StaticResource Sidebar}" <!-- ============ THE BORDERED BODY ============ -->
BorderBrush="{StaticResource Border}" BorderThickness="1,0,0,0"> <Border Grid.Row="1" BorderBrush="{StaticResource Border}" BorderThickness="1" CornerRadius="12"
<ScrollViewer> ClipToBounds="True">
<StackPanel Margin="14,16" Spacing="8"> <Grid ColumnDefinitions="*,340">
<!-- ============ The editor ============ --> <Grid Grid.Column="0" RowDefinitions="*,Auto" Background="{StaticResource Pane}">
<StackPanel Spacing="6" IsVisible="{Binding IsEditing}">
<TextBox Text="{Binding EditorLabel}" PlaceholderText="name" />
<!-- <ListBox Grid.Row="0" x:Name="SnippetList" Classes="snipcards" Focusable="True"
Which vault a *new* snippet is filed into, asked on the form it is being typed into rather ItemsSource="{Binding Visible}"
than through a standing preference elsewhere. Not drawn for an existing snippet — its vault is SelectedItem="{Binding Selected}"
not a field of this form, and changing it is MOVE — and not drawn at all where there is only Margin="10,10,10,4">
one vault to choose between, because a control offering one option is a question nobody asked. <ListBox.ItemTemplate>
--> <DataTemplate x:DataType="vm:SnippetRowViewModel">
<StackPanel Spacing="4" IsVisible="{Binding ShowsEditorVaultChoice}"> <StackPanel Margin="6,7" Spacing="6">
<TextBlock Classes="label" Text="VAULT" /> <StackPanel Orientation="Horizontal" Spacing="8">
<ComboBox HorizontalAlignment="Stretch" ItemsSource="{Binding EditorVaultChoices}" <TextBlock Classes="mono" Text="{}{ }" FontSize="11" FontWeight="Bold"
SelectedItem="{Binding EditorSelectedVault}"> Foreground="{StaticResource AccentText}" VerticalAlignment="Center" />
<ComboBox.ItemTemplate> <TextBlock Classes="mono" Text="{Binding Label}" FontSize="13" FontWeight="Medium"
<DataTemplate x:DataType="vm:VaultChoiceViewModel"> Foreground="{StaticResource Text}" VerticalAlignment="Center"
<TextBlock Text="{Binding Display}" FontSize="12" /> TextTrimming="CharacterEllipsis" />
</DataTemplate> <!--
</ComboBox.ItemTemplate> The flag, where the decision is made. A snippet that presses Enter for you is not the
</ComboBox> same kind of thing as one that does not, and the list is where somebody chooses between
<TextBlock Classes="hint" FontSize="10.5" TextWrapping="Wrap" them.
Text="A shared vault means everybody holding its key can read this command and insert it into their own terminals." /> -->
<Border Classes="chip warn" Padding="7,0" Height="18" VerticalAlignment="Center"
IsVisible="{Binding RunsOnInsert}">
<TextBlock Text="runs immediately" FontSize="9.5" />
</Border>
<Border Classes="chip warn" Padding="7,0" Height="18" VerticalAlignment="Center"
IsVisible="{Binding Badge, Converter={x:Static StringConverters.IsNotNullOrEmpty}}">
<TextBlock Text="{Binding Badge}" FontSize="9.5" />
</Border>
<!--
Which vault this one is in, drawn only where there is more than one to be in. A shared
snippet is a command the rest of a team can read and insert; the row is where somebody
decides whether the thing they are about to edit is theirs alone.
-->
<Border Classes="chip" Padding="7,0" Height="18" VerticalAlignment="Center"
IsVisible="{Binding HasVaultBadge}">
<TextBlock Text="{Binding VaultBadge}" FontSize="9.5" />
</Border>
</StackPanel>
<!--
Newlines shown as ⏎ rather than dropped. A three-line snippet flattened into one run of
text reads as a single command, which is the thing being decided about on this row.
-->
<TextBlock Classes="mono" Text="{Binding Preview}" FontSize="11.5"
Foreground="{StaticResource TextFaint}" TextTrimming="CharacterEllipsis" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<TextBlock Grid.Row="0" Classes="hint" Text="{Binding EmptyMessage}" FontSize="12"
Margin="24" HorizontalAlignment="Center" VerticalAlignment="Center"
TextAlignment="Center" MaxWidth="360"
IsVisible="{Binding !HasVisible}" />
<Border Grid.Row="1" Padding="16,10" BorderBrush="{StaticResource BorderSubtle}"
BorderThickness="0,1,0,0">
<StackPanel Orientation="Horizontal" Spacing="8">
<Button Classes="ghost" Content="Edit" Command="{Binding EditCommand}"
IsEnabled="{Binding HasSelection}" />
<!--
Sharing a snippet, which is what moving one into a team's vault is. Beside EDIT rather than
inside it: the two vaults are encrypted under different keys, so this is a re-seal into one
and a tombstone in the other — nothing a SAVE could do — and a picker inside the form would
let somebody correcting a typo hand a command to a team by leaving it where they found it.
The picker itself opens beside the snippet, in the pane on the right.
-->
<Button Classes="ghost" Content="Move to vault…" Command="{Binding MoveCommand}"
IsEnabled="{Binding CanMove}"
ToolTip.Tip="Re-encrypts this snippet with another vault's key. Everybody who holds that key can then read and insert it." />
<Button Classes="danger" Content="Delete" Command="{Binding RequestDeleteCommand}"
IsEnabled="{Binding HasSelection}" />
</StackPanel> </StackPanel>
</Border>
<!-- </Grid>
Stored exactly as typed — no trimming, no newline normalisation. A here-document's terminator
has to arrive on a line of its own, and tidying the trailing newline off it leaves the shell
waiting for one that never comes.
-->
<TextBox Text="{Binding EditorCommand}" PlaceholderText="the command" AcceptsReturn="True"
Height="140" TextWrapping="NoWrap" FontFamily="{StaticResource MonoFont}"
FontSize="12" />
<TextBox Text="{Binding EditorNotes}" PlaceholderText="notes" AcceptsReturn="True"
Height="48" TextWrapping="Wrap" />
<CheckBox IsChecked="{Binding EditorRunsOnInsert}"
Content="Press Enter after inserting this" />
<TextBlock Classes="hint" FontSize="11" TextWrapping="Wrap"
Text="Off means the command is typed at the prompt and waits for you. That single Enter is the only thing standing between a saved command and a running one, so leave it off unless you meant it." />
<StackPanel Orientation="Horizontal" Spacing="6">
<Button Classes="accent" Content="SAVE" Command="{Binding SaveCommand}" />
<Button Classes="ghost" Content="CANCEL" Command="{Binding CancelCommand}" />
</StackPanel>
</StackPanel>
<!-- ============ The selected snippet ============ --> <Border Grid.Column="1" Background="{StaticResource Sidebar}"
<StackPanel Spacing="6" IsVisible="{Binding !IsEditing}"> BorderBrush="{StaticResource Border}" BorderThickness="1,0,0,0">
<ScrollViewer>
<StackPanel Margin="18,20" Spacing="10">
<TextBlock Classes="hint" FontSize="12" <!-- ============ The editor ============ -->
Text="Choose a snippet to see it in full and put it into a terminal." <StackPanel Spacing="8" IsVisible="{Binding IsEditing}">
IsVisible="{Binding !HasSelection}" /> <TextBlock Classes="label" Text="NAME" FontSize="10" />
<TextBox Text="{Binding EditorLabel}" PlaceholderText="name" FontFamily="{StaticResource MonoFont}" />
<StackPanel Spacing="6" IsVisible="{Binding HasSelection}"> <!--
<TextBlock Classes="mono" Text="{Binding Selected.Label}" FontSize="13" Which vault a *new* snippet is filed into, asked on the form it is being typed into rather
FontWeight="SemiBold" Foreground="{StaticResource Text}" TextWrapping="Wrap" /> than through a standing preference elsewhere. Not drawn for an existing snippet — its vault
is not a field of this form, and changing it is MOVE — and not drawn at all where there is
only one vault to choose between, because a control offering one option is a question
nobody asked.
-->
<StackPanel Spacing="4" IsVisible="{Binding ShowsEditorVaultChoice}">
<TextBlock Classes="label" Text="VAULT" FontSize="10" />
<ComboBox HorizontalAlignment="Stretch" ItemsSource="{Binding EditorVaultChoices}"
SelectedItem="{Binding EditorSelectedVault}">
<ComboBox.ItemTemplate>
<DataTemplate x:DataType="vm:VaultChoiceViewModel">
<TextBlock Text="{Binding Display}" FontSize="12" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<TextBlock Classes="hint" FontSize="10.5" TextWrapping="Wrap"
Text="A shared vault means everybody holding its key can read this command and insert it into their own terminals." />
</StackPanel>
<!-- <TextBlock Classes="label" Text="COMMAND" FontSize="10" />
Named here as well as on the row, because this pane is where somebody decides to put a <!--
command into a production terminal, and who else holds a key to it is part of that decision. Stored exactly as typed — no trimming, no newline normalisation. A here-document's
The row's badge is off the screen by the time this is being read. terminator has to arrive on a line of its own, and tidying the trailing newline off it
--> leaves the shell waiting for one that never comes.
<Border Classes="chip" Padding="4,0" HorizontalAlignment="Left" -->
IsVisible="{Binding HasSelectionVaultBadge}"> <TextBox Text="{Binding EditorCommand}" PlaceholderText="the command" AcceptsReturn="True"
<TextBlock Text="{Binding SelectionVaultBadge}" FontSize="9.5" /> Height="140" TextWrapping="NoWrap" FontFamily="{StaticResource MonoFont}"
</Border> FontSize="12" />
<TextBox Text="{Binding EditorNotes}" PlaceholderText="notes" AcceptsReturn="True"
Height="48" TextWrapping="Wrap" />
<TextBlock Classes="label" Text="COMMAND" Margin="0,10,0,4" /> <Grid ColumnDefinitions="*,Auto" Margin="0,4,0,0">
<Border Background="{StaticResource Raised}" BorderBrush="{StaticResource Border}" <StackPanel Grid.Column="0" Spacing="3">
BorderThickness="1" CornerRadius="4" Padding="8"> <TextBlock Text="Runs on insert" FontSize="12.5" FontWeight="SemiBold"
<SelectableTextBlock Classes="mono" Text="{Binding Selected.Snippet.Command}" Foreground="{StaticResource TextDim}" />
FontSize="10.5" Foreground="{StaticResource TextDim}" <TextBlock Classes="hint" FontSize="10.5" TextWrapping="Wrap"
TextWrapping="Wrap" /> Text="Off means the command is typed at the prompt and waits for you. That single Enter is the only thing standing between a saved command and a running one, so leave it off unless you meant it." />
</Border> </StackPanel>
<CheckBox Grid.Column="1" IsChecked="{Binding EditorRunsOnInsert}"
VerticalAlignment="Top" Margin="10,0,0,0" />
</Grid>
<TextBlock Classes="mono" Text="{Binding Selected.Snippet.Notes}" FontSize="11" <StackPanel Orientation="Horizontal" Spacing="6" Margin="0,6,0,0">
Foreground="{StaticResource TextFaint}" TextWrapping="Wrap" Margin="0,6,0,0" <Button Classes="accent" Content="Save" Command="{Binding SaveCommand}" />
IsVisible="{Binding Selected.Snippet.Notes, Converter={x:Static StringConverters.IsNotNullOrEmpty}}" /> <Button Classes="ghost" Content="Cancel" Command="{Binding CancelCommand}" />
<!--
The button names the tab it will type into. This screen is not the terminal — the strip
above it is — so "INSERT" alone would leave somebody working out which of six open tabs is
about to receive a command, at the moment that is worst to be wrong about.
-->
<StackPanel Spacing="6" IsVisible="{Binding ShowsSelectionActions}">
<Button Classes="accent" Content="{Binding InsertLabel}" Margin="0,14,0,0"
HorizontalAlignment="Left"
Command="{Binding InsertCommand}" IsEnabled="{Binding CanInsert}"
ToolTip.Tip="Types the command at the prompt and stops. Nothing runs until you press Enter there." />
<Button Classes="danger" Content="{Binding RunLabel}" HorizontalAlignment="Left"
Command="{Binding RunCommand}"
IsVisible="{Binding SelectionRuns}" IsEnabled="{Binding CanInsert}"
ToolTip.Tip="Types the command and presses Enter. Offered because this snippet is marked as one that runs." />
<TextBlock Classes="hint" FontSize="11" TextWrapping="Wrap" Margin="0,10,0,0"
Text="Whatever is in the terminal receives this. Nothing here can tell whether that is a shell prompt, an editor, or a password prompt with the echo off — so check the tab before you insert." />
</StackPanel>
<!--
◆ SHARING THE SNIPPET, which is what moving it into a team's vault amounts to. A picker and
two buttons, not a question with a yes: what is being asked is which vault, and a move is
undone by moving it back rather than by being careful — so this is not drawn in the danger
colours the deletion question uses.
It takes the insert controls' place while it is up, for the reason the host pane hides
CONNECT: the button that opened this is still on screen otherwise, offering to open it again.
The sentence is the part worth keeping. Unlike a host, a snippet crosses whole — it has no
group and no tags to leave behind — so what there is to say is who can read it afterwards,
and for a command that may carry a hostname or a path that is the whole of the decision.
-->
<StackPanel Spacing="8" Margin="0,14,0,0" IsVisible="{Binding IsMoving}">
<TextBlock Classes="label" Text="MOVE TO VAULT" />
<ComboBox HorizontalAlignment="Stretch" ItemsSource="{Binding MoveVaultChoices}"
SelectedItem="{Binding SelectedMoveVault}">
<ComboBox.ItemTemplate>
<DataTemplate x:DataType="vm:VaultChoiceViewModel">
<TextBlock Text="{Binding Display}" FontSize="12" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<TextBlock Classes="hint" FontSize="10.5" TextWrapping="Wrap"
Text="The snippet is re-encrypted with the other vault's key, so everybody who holds that key can read this command and insert it — and nobody else can. Nothing else about it changes." />
<StackPanel Orientation="Horizontal" Spacing="6">
<Button Classes="accent" Content="MOVE" Command="{Binding ConfirmMoveCommand}" />
<Button Classes="ghost" Content="CANCEL" Command="{Binding CancelMoveCommand}" />
</StackPanel> </StackPanel>
</StackPanel> </StackPanel>
</StackPanel>
</StackPanel>
</StackPanel> <!-- ============ The selected snippet ============ -->
</ScrollViewer> <StackPanel Spacing="10" IsVisible="{Binding !IsEditing}">
<TextBlock Classes="hint" FontSize="12"
Text="Choose a snippet to see it in full and put it into a terminal."
IsVisible="{Binding !HasSelection}" />
<StackPanel Spacing="10" IsVisible="{Binding HasSelection}">
<TextBlock Classes="mono" Text="{Binding Selected.Label}" FontSize="15"
FontWeight="Bold" Foreground="{StaticResource Text}" TextWrapping="Wrap" />
<TextBlock Classes="label" Text="COMMAND" FontSize="10" />
<Border CornerRadius="10" Background="{StaticResource Pane}"
BorderBrush="{StaticResource BorderMid}" BorderThickness="1" Padding="12">
<SelectableTextBlock Classes="mono" Text="{Binding Selected.Snippet.Command}"
FontSize="11.5" LineHeight="19"
Foreground="{StaticResource TextFaint}" TextWrapping="Wrap" />
</Border>
<TextBlock Classes="mono" Text="{Binding Selected.Snippet.Notes}" FontSize="11"
Foreground="{StaticResource TextFaint}" TextWrapping="Wrap"
IsVisible="{Binding Selected.Snippet.Notes, Converter={x:Static StringConverters.IsNotNullOrEmpty}}" />
<!--
VAULT, with the design's own lock glyph. Named here as well as on the row, because this
pane is where somebody decides to put a command into a production terminal, and who else
holds a key to it is part of that decision.
-->
<StackPanel Spacing="4" IsVisible="{Binding HasSelectionVaultBadge}">
<TextBlock Classes="label" Text="VAULT" FontSize="10" />
<Border Height="38" CornerRadius="10" Background="{StaticResource Pane}"
BorderBrush="{StaticResource BorderMid}" BorderThickness="1" Padding="12,0">
<Grid ColumnDefinitions="*,Auto">
<TextBlock Grid.Column="0" Classes="mono" Text="{Binding SelectionVaultBadge}"
FontSize="11.5" Foreground="{StaticResource TextDim}"
VerticalAlignment="Center" />
<TextBlock Grid.Column="1" FontFamily="{StaticResource IconFont}" Text="&#xE897;"
FontSize="14" Foreground="{StaticResource TextFaint}"
VerticalAlignment="Center" />
</Grid>
</Border>
</StackPanel>
<!--
The button names the tab it will type into, over the design's static "Type into
terminal" — see the file-level remark above for why the dynamic label is the truer one.
-->
<StackPanel Spacing="6" IsVisible="{Binding ShowsSelectionActions}">
<Button Classes="accent" Content="{Binding InsertLabel}" HorizontalAlignment="Stretch"
HorizontalContentAlignment="Center"
Command="{Binding InsertCommand}" IsEnabled="{Binding CanInsert}"
ToolTip.Tip="Types the command at the prompt and stops. Nothing runs until you press Enter there." />
<Button Classes="danger" Content="{Binding RunLabel}" HorizontalAlignment="Stretch"
HorizontalContentAlignment="Center"
Command="{Binding RunCommand}"
IsVisible="{Binding SelectionRuns}" IsEnabled="{Binding CanInsert}"
ToolTip.Tip="Types the command and presses Enter. Offered because this snippet is marked as one that runs." />
<TextBlock Classes="hint" FontSize="10.5" TextWrapping="Wrap" TextAlignment="Center"
Text="Whatever is in the terminal receives this. Nothing here can tell whether that is a shell prompt, an editor, or a password prompt with the echo off — so check the tab before you insert." />
</StackPanel>
<!--
◆ SHARING THE SNIPPET, which is what moving it into a team's vault amounts to. A picker
and two buttons, not a question with a yes.
-->
<StackPanel Spacing="8" IsVisible="{Binding IsMoving}">
<TextBlock Classes="label" Text="MOVE TO VAULT" FontSize="10" />
<ComboBox HorizontalAlignment="Stretch" ItemsSource="{Binding MoveVaultChoices}"
SelectedItem="{Binding SelectedMoveVault}">
<ComboBox.ItemTemplate>
<DataTemplate x:DataType="vm:VaultChoiceViewModel">
<TextBlock Text="{Binding Display}" FontSize="12" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<TextBlock Classes="hint" FontSize="10.5" TextWrapping="Wrap"
Text="The snippet is re-encrypted with the other vault's key, so everybody who holds that key can read this command and insert it — and nobody else can. Nothing else about it changes." />
<StackPanel Orientation="Horizontal" Spacing="6">
<Button Classes="accent" Content="Move" Command="{Binding ConfirmMoveCommand}" />
<Button Classes="ghost" Content="Cancel" Command="{Binding CancelMoveCommand}" />
</StackPanel>
</StackPanel>
<!--
◆ THE DELETE CONFIRMATION. See RequestDelete's own remarks for why this is additive
beside DeleteCommand rather than a change to it.
-->
<Border Background="{StaticResource DangerWash}" BorderBrush="{StaticResource DangerSoft}"
BorderThickness="1" CornerRadius="10" Padding="12"
IsVisible="{Binding IsConfirmingDelete}">
<StackPanel Spacing="8">
<TextBlock Classes="heading" FontSize="14" TextWrapping="Wrap"
Text="{Binding DeleteQuestion}" />
<TextBlock Foreground="{StaticResource WarnText}" FontSize="12" TextWrapping="Wrap"
Text="{Binding DeleteConsequence}" />
<StackPanel Orientation="Horizontal" Spacing="6">
<Button Classes="danger" Content="Delete snippet"
Command="{Binding ConfirmDeleteCommand}" />
<Button Classes="ghost" Content="Cancel" Command="{Binding CancelDeleteCommand}" />
</StackPanel>
</StackPanel>
</Border>
</StackPanel>
</StackPanel>
</StackPanel>
</ScrollViewer>
</Border>
</Grid>
</Border> </Border>
</Grid> </Grid>