<UserControl xmlns="https://github.com/avaloniaui"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:vm="using:DodoSSH.Client.Shell.ViewModels"
             xmlns:views="using:DodoSSH.Client.Android.Views"
             x:Class="DodoSSH.Client.Android.Views.HostsScreen"
             x:DataType="vm:VaultViewModel"
             Background="{StaticResource Canvas}">

  <!--
    Design 01 — HOSTS, the phone's home.

    The desktop's sidebar and its right-hand connect column cannot both be on screen at 360dp, so the list
    fills the screen and everything else is raised over it or put in its place.

    Groups are rows in the same list rather than a nesting container, which is what SidebarRows already
    hands both heads. The desktop shows group headings only when groups exist and so does this, for free.

    ── v2 ────────────────────────────────────────────────────────────────────────────────────────────────
    The rows became cards: rounded, inset from the edge, and filled when chosen rather than marked with a
    strip.

    ── v3 ────────────────────────────────────────────────────────────────────────────────────────────────
    The design's floating + arrives, three versions after it was first asked for. It was refused twice on
    the grounds that it would open an editor this head had not got — hosts were created on the desktop and
    synced down — and that was true until the editors below existed. It opens a sheet rather than a form,
    because "add" on this screen has been two operations since groups existed: a host, or a group to file
    hosts under.

    ── v4: a tap connects, and a long press raises a bar. ───────────────────────────────────────────────
    Choosing a row used to raise a connect bar over the bottom of the list — a password box, CONNECT, EDIT,
    MOVE and DELETE, five controls in the way of the one thing a tap on a machine's name means. So the
    gestures split: a tap connected, and a long press raised the bar.

    ── ◆ v5: THE BAR IS GONE, AND A LONG PRESS CHOOSES HOSTS. ───────────────────────────────────────────
    The bar was the right idea in the wrong place. It sat over the bottom of the list, so it covered rows; it
    had room for five things and never a sixth; and every one of them was about exactly one machine, which
    left filing eleven imported hosts under a group as eleven trips through a form. What replaced it:

      · a tap still connects, and raises nothing — see VaultViewModel.ConnectToRowAsync;
      · a long press puts a tick against the host it landed on, and further taps tick and untick;
      · the actions move into a bar across the top of the screen, in the vault header's place, carrying the
        count, a pencil and a ⋯ — see HostActionBar.axaml and PhoneShell.ShowsHostSelectionBar;
      · the seven things behind that ⋯ are the sheet at the foot of this file, and the three of them that
        ask a question first raise a panel above the list rather than over it.

    Two pieces of the old bar survive here, both because they had nowhere else to be. A host that
    authenticates with a typed password cannot be reached by a tap alone, so that tap raises a sheet with the
    box in it. And the editor, which used to be a card in the list's own row, is now a page over the whole
    screen — the pencil in the action bar opens it, and PhoneShell stands its own chrome down for it.
  -->

  <!--
    The Panel is what lets the sheets and the editor page cover the screen. The + does not need it — it is
    layered inside the list's own Grid row, which is where it has to be so that a panel raised above the list
    shortens it rather than being covered by it. See the button itself.
  -->
  <Panel>

  <Grid RowDefinitions="Auto,Auto,*">

    <!-- ============ filter ============ -->
    <!--
      The design's "Search or connect…" well. On the desktop this is Ctrl+K over a palette; on a phone the
      list is already the palette, so it filters in place rather than opening a second surface.

      It stays up while hosts are ticked, deliberately. Filtering does not clear the set — the ticks are
      held as ids rather than as rows, see VaultViewModel.chosenHostIds — so searching for the second half
      of a selection and ticking it is a thing this screen supports rather than merely survives.
    -->
    <Border Grid.Row="0" Margin="14,10,14,4" Background="{StaticResource Field}"
            BorderBrush="{StaticResource BorderMid}" BorderThickness="1" CornerRadius="10" Height="44">
      <Grid ColumnDefinitions="Auto,*">
        <TextBlock Grid.Column="0" Text="⌕" Foreground="{StaticResource TextFaint}" FontSize="13"
                   Margin="13,0,0,0" VerticalAlignment="Center" />
        <TextBox Grid.Column="1" Text="{Binding HostFilter}" PlaceholderText="Search hosts"
                 Padding="9,0" VerticalContentAlignment="Center"
                 Background="Transparent" BorderThickness="0"
                 Foreground="{StaticResource Text}" FontFamily="{StaticResource MonoFont}" FontSize="12" />
      </Grid>
    </Border>

    <!-- ============ sync + conflicts ============ -->
    <StackPanel Grid.Row="1" Margin="16,4,16,0" Spacing="7">

      <TextBlock Classes="detail" Text="{Binding Status}" TextWrapping="Wrap" />

      <!--
        ◆ The conflict log. The merge is only allowed to pick a winner because this exists, so it is a
        banner the user has to dismiss rather than a screen they might never open.
      -->
      <Border IsVisible="{Binding HasConflicts}" Background="{StaticResource WarnWash}"
              BorderBrush="{StaticResource WarnSoft}" BorderThickness="1" CornerRadius="12" Padding="13,11">
        <StackPanel Spacing="9">
          <ItemsControl ItemsSource="{Binding Conflicts}">
            <ItemsControl.ItemTemplate>
              <DataTemplate x:DataType="vm:ConflictRowViewModel">
                <TextBlock Classes="detail" Foreground="{StaticResource WarnText}" TextWrapping="Wrap"
                           Text="{Binding Summary}" Margin="0,2" />
              </DataTemplate>
            </ItemsControl.ItemTemplate>
          </ItemsControl>
          <Button Classes="secondary" Height="40" Content="DISMISS ALL" FontSize="10"
                  Command="{Binding AcknowledgeAllConflictsCommand}" />
        </StackPanel>
      </Border>

      <Border IsVisible="{Binding HasUnreadableItems}" Background="{StaticResource DangerWash}"
              BorderBrush="{StaticResource DangerSoft}" BorderThickness="1" CornerRadius="12" Padding="13,11">
        <TextBlock Classes="detail" Foreground="{StaticResource DangerText}" TextWrapping="Wrap"
                   Text="{Binding UnreadableSummary}" />
      </Border>

      <!-- ============ ◆ what the action bar's menu asked ============ -->
      <!--
        THREE PANELS, AT MOST ONE OF THEM UP, AND ALL THREE ABOVE THE LIST RATHER THAN OVER IT.

        Same arrangement as the group panels below, and for the reason written there: a card on top of the
        list hides the rows it is talking about, and those rows — the ticked ones — are the information the
        question exists to give. The list shortens instead, which keeps the ticks in view while the question
        is answered.

        Which of the three is showing is decided in the view model rather than here, because each of them
        disarms the other two on the way up. See MoveChosenHostsToVault, RegroupChosenHosts and
        DeleteChosenHosts, all of which clear the other two before setting their own.
      -->

      <!--
        ◆ MOVING OR COPYING TO ANOTHER VAULT. One panel for both verbs, because they differ in one word and
        in the sentence under the picker; two copies of this would be two places to fix the day a third
        vault rule arrives. What varies is bound rather than duplicated — see
        VaultViewModel.ChosenHostVaultPanelTitle and its two siblings.

        The sentence is not decoration. Groups and tags are items of the vault being left, so neither can
        come along: a host that arrived carrying either would point at something the destination does not
        contain, and everybody but the person who moved it would see it filed under nothing. On a phone,
        where the status line afterwards is one line at the bottom of a screen somebody has already
        navigated away from, saying it before the tap is the only place it reliably gets read.
      -->
      <Border IsVisible="{Binding IsSendingChosenHostsToAVault}" Background="{StaticResource Panel}"
              BorderBrush="{StaticResource BorderMid}" BorderThickness="1" CornerRadius="12"
              Padding="13,12">
        <StackPanel Spacing="9">
          <StackPanel Orientation="Horizontal" Spacing="8">
            <TextBlock Classes="label" Text="{Binding ChosenHostVaultPanelTitle}" />
            <TextBlock Classes="mono" FontSize="11" VerticalAlignment="Center"
                       Text="{Binding ChosenHostsLabel}" />
          </StackPanel>

          <ComboBox HorizontalAlignment="Stretch" MinHeight="44"
                    ItemsSource="{Binding ChosenHostVaultChoices}"
                    SelectedItem="{Binding SelectedChosenHostVault}">
            <ComboBox.ItemTemplate>
              <DataTemplate x:DataType="vm:VaultChoiceViewModel">
                <TextBlock Classes="mono" FontSize="12" Text="{Binding Display}" />
              </DataTemplate>
            </ComboBox.ItemTemplate>
          </ComboBox>

          <TextBlock Classes="body" Text="{Binding ChosenHostVaultPanelNote}" />

          <!--
            ◆ THE KEY. A binding resolves across vaults, so the host goes on working here whichever way this
            is answered — but the vault it has just joined holds one key, and a host whose own key stayed
            behind is one its new colleagues cannot connect with.

            Drawn only for a move of exactly one host, which is the only shape the question has an answer in:
            which key to carry is a fact about one machine, and a copy that took the key away would leave the
            original unable to connect. See VaultViewModel.BringsTheChosenBindingAlong.

            Unticked, because moving a key into a team's vault hands it to everybody who holds that vault's
            key: a disclosure is chosen, never defaulted into. The line under it is the count of what else
            uses that key, which is the difference between an obvious yes and an obvious no.
          -->
          <CheckBox IsChecked="{Binding BringsTheChosenBindingAlong}" MinHeight="44"
                    IsVisible="{Binding HasAChosenBindingToBring}">
            <TextBlock Classes="mono" FontSize="11.5" TextWrapping="Wrap"
                       Text="{Binding ChosenBindingToBringQuestion}" />
          </CheckBox>
          <TextBlock Classes="body" TextWrapping="Wrap"
                     IsVisible="{Binding HasAChosenBindingToBring}"
                     Text="{Binding ChosenBindingToBringNote}" />

          <Grid ColumnDefinitions="*,8,*">
            <Button Grid.Column="0" Classes="primary" Height="44"
                    Content="{Binding ChosenHostVaultPanelVerb}"
                    Command="{Binding ConfirmSendChosenHostsToAVaultCommand}"
                    IsEnabled="{Binding !IsBusy}" />
            <Button Grid.Column="2" Classes="secondary" Height="44" Content="CANCEL"
                    Command="{Binding CancelSendChosenHostsToAVaultCommand}" />
          </Grid>
        </StackPanel>
      </Border>

      <!--
        ◆ FILING THE TICKED HOSTS UNDER A GROUP. The one action in the bar that has always existed with no
        control on this head: it is the same write dragging a card onto a group makes on the desktop, and
        there is no dragging here.

        One keychain's groups, and a selection spanning two is refused before this panel is drawn at all —
        a group belongs to one vault, so filing a host from another under it would leave everybody but the
        person who did it seeing a machine filed under nothing. See VaultViewModel.RegroupChosenHosts.
      -->
      <Border IsVisible="{Binding IsRegroupingChosenHosts}" Background="{StaticResource Panel}"
              BorderBrush="{StaticResource BorderMid}" BorderThickness="1" CornerRadius="12"
              Padding="13,12">
        <StackPanel Spacing="9">
          <StackPanel Orientation="Horizontal" Spacing="8">
            <TextBlock Classes="label" Text="CHANGE GROUP" />
            <TextBlock Classes="mono" FontSize="11" VerticalAlignment="Center"
                       Text="{Binding ChosenHostsLabel}" />
          </StackPanel>

          <ComboBox HorizontalAlignment="Stretch" MinHeight="44"
                    ItemsSource="{Binding ChosenHostGroupChoices}"
                    SelectedItem="{Binding SelectedChosenHostGroup}">
            <ComboBox.ItemTemplate>
              <DataTemplate x:DataType="vm:GroupChoice">
                <TextBlock Classes="mono" FontSize="12" Text="{Binding Label}" />
              </DataTemplate>
            </ComboBox.ItemTemplate>
          </ComboBox>

          <TextBlock Classes="body"
                     Text="A group lends its port, its account and its key to every host filed under it that says nothing itself, so this can change what these machines dial. Nothing else about them moves." />

          <Grid ColumnDefinitions="*,8,*">
            <Button Grid.Column="0" Classes="primary" Height="44" Content="FILE"
                    Command="{Binding ConfirmRegroupChosenHostsCommand}" IsEnabled="{Binding !IsBusy}" />
            <Button Grid.Column="2" Classes="secondary" Height="44" Content="CANCEL"
                    Command="{Binding CancelRegroupChosenHostsCommand}" />
          </Grid>
        </StackPanel>
      </Border>

      <!--
        ◆ THE DELETION QUESTION, naming a count rather than a machine. One question for the whole set is the
        reason DeletionTarget.ChosenHosts exists: six copies of "delete prod-db?" is not a confirmation
        anybody reads, and the number is the difference between a sentence somebody reads and one they press
        past.
      -->
      <Border IsVisible="{Binding IsConfirmingChosenHostDeletion}" Background="{StaticResource DangerWash}"
              BorderBrush="{StaticResource DangerSoft}" BorderThickness="1" CornerRadius="12" Padding="13,12">
        <StackPanel Spacing="6">
          <TextBlock Classes="mono" FontSize="12" TextWrapping="Wrap"
                     Text="{Binding PendingDeletion.Question}" />
          <TextBlock Classes="body" Foreground="{StaticResource DangerText}"
                     Text="{Binding PendingDeletion.Consequence}" />
          <TextBlock Classes="body" Foreground="{StaticResource DangerText}"
                     IsVisible="{Binding PendingDeletion.HasUsage}"
                     Text="{Binding PendingDeletion.Usage}" />

          <Grid ColumnDefinitions="*,8,*" Margin="0,4,0,0">
            <Button Grid.Column="0" Classes="danger" Height="44" Content="DELETE"
                    Command="{Binding ConfirmDeleteCommand}" IsEnabled="{Binding !IsBusy}" />
            <Button Grid.Column="2" Classes="secondary" Height="44" Content="KEEP" FontSize="10.5"
                    Command="{Binding CancelDeleteCommand}" />
          </Grid>
        </StackPanel>
      </Border>

      <!-- ============ ◆ what the sheet raised about a group ============ -->
      <!--
        Both of these are about a heading in the list below, and both are drawn above it rather than over it
        — which is what KEYCHAIN does with its own confirmation and for the reason written there: a card on
        top of the list hides the row it is talking about, and the row is the information the question exists
        to give. Neither replaces the list, because neither replaces the heading either.

        At most one of the two is ever up. MoveGroup disarms a deletion and DeleteGroup folds the move away,
        which is the same pairing the desktop's GROUPS section makes.

        ◆ THE DELETION QUESTION, and the tick under it is the whole reason it is a question rather than a
        warning. Off is "the machines stay and turn up under UNGROUPED", which is recoverable; on is not, and
        it is reset to off on every question — see VaultViewModel.DeletionTakesTheHostsToo. Below the count
        rather than above it, so nobody answers it before reading how many.
      -->
      <Border IsVisible="{Binding IsConfirmingGroupDeletion}" Background="{StaticResource DangerWash}"
              BorderBrush="{StaticResource DangerSoft}" BorderThickness="1" CornerRadius="12" Padding="13,12">
        <StackPanel Spacing="6">
          <TextBlock Classes="mono" FontSize="12" TextWrapping="Wrap"
                     Text="{Binding PendingDeletion.Question}" />
          <TextBlock Classes="body" Foreground="{StaticResource DangerText}"
                     Text="{Binding PendingDeletion.Consequence}" />
          <TextBlock Classes="body" Foreground="{StaticResource DangerText}"
                     IsVisible="{Binding PendingDeletion.HasUsage}"
                     Text="{Binding PendingDeletion.Usage}" />

          <CheckBox MinHeight="44" IsVisible="{Binding PendingDeletion.HasChoice}"
                    IsChecked="{Binding DeletionTakesTheHostsToo}">
            <TextBlock Classes="mono" FontSize="11.5" TextWrapping="Wrap"
                       Foreground="{StaticResource WarnText}" Text="{Binding PendingDeletion.Choice}" />
          </CheckBox>

          <Grid ColumnDefinitions="*,8,*" Margin="0,4,0,0">
            <Button Grid.Column="0" Classes="danger" Height="44" Content="DELETE"
                    Command="{Binding ConfirmDeleteCommand}" IsEnabled="{Binding !IsBusy}" />
            <Button Grid.Column="2" Classes="secondary" Height="44" Content="KEEP" FontSize="10.5"
                    Command="{Binding CancelDeleteCommand}" />
          </Grid>
        </StackPanel>
      </Border>

      <!--
        ◆ MOVING THE GROUP TO ANOTHER VAULT. The host's panel one level up, and not drawn in the danger
        colours beside it: what is being asked is which vault, and a move is undone by moving it back.

        It names the group, which the desktop's copy of this panel does not have to — there the group's own
        card is on screen underneath the picker, and here the panel is above a list the heading may have
        scrolled out of. See VaultViewModel.MovingGroupLabel.

        The sentence is not decoration. A group cannot go anywhere alone: the groups nested inside it and
        every machine filed under any of them are items of the vault it is leaving, so all of them are
        re-sealed under the destination's key. What cannot come is the group it was nested under, which is why
        it arrives at the top level, and the hosts' tags stay behind with it. On a phone the status line
        afterwards is one line at the bottom of a screen somebody has already navigated away from, so before
        the tap is the only place this reliably gets read.
      -->
      <Border IsVisible="{Binding IsMovingGroup}" Background="{StaticResource Panel}"
              BorderBrush="{StaticResource BorderMid}" BorderThickness="1" CornerRadius="12"
              Padding="13,12">
        <StackPanel Spacing="9">
          <StackPanel Orientation="Horizontal" Spacing="8">
            <TextBlock Classes="label" Text="MOVE GROUP" />
            <TextBlock Classes="mono" FontSize="11" Text="{Binding MovingGroupLabel}"
                       TextTrimming="CharacterEllipsis" VerticalAlignment="Center" />
          </StackPanel>

          <ComboBox HorizontalAlignment="Stretch" MinHeight="44"
                    ItemsSource="{Binding MoveGroupVaultChoices}"
                    SelectedItem="{Binding SelectedMoveGroupVault}">
            <ComboBox.ItemTemplate>
              <DataTemplate x:DataType="vm:VaultChoiceViewModel">
                <TextBlock Classes="mono" FontSize="12" Text="{Binding Display}" />
              </DataTemplate>
            </ComboBox.ItemTemplate>
          </ComboBox>

          <TextBlock Classes="body"
                     Text="The group goes with the groups inside it and every host filed under them, all re-encrypted with the other vault's key. Any group it is nested under stays behind, so it arrives at the top level, and the hosts' tags stay behind with it." />

          <Grid ColumnDefinitions="*,8,*">
            <Button Grid.Column="0" Classes="primary" Height="44" Content="MOVE"
                    Command="{Binding ConfirmMoveGroupCommand}" IsEnabled="{Binding !IsBusy}" />
            <Button Grid.Column="2" Classes="secondary" Height="44" Content="CANCEL"
                    Command="{Binding CancelMoveGroupCommand}" />
          </Grid>
        </StackPanel>
      </Border>
    </StackPanel>

    <!-- ============ the list ============ -->
    <!--
      ◆ Named, because both gestures are attached from code-behind now: the long press only fires at all once
      IsHoldingEnabled is on, and the tap has a branch in it that the markup cannot express — it connects, or
      it ticks, depending on whether anything is ticked already. Setting the attached property and both
      handlers in one place is what keeps the three from drifting apart. See HostsScreen.axaml.cs.

      The row stays a plain Border rather than becoming a Button, for the reason FilesScreen writes out: a
      button as the item template swallows the press before the list sees it, so neither handler would ever
      run.

      ◆ THE SELECTION FILL IS GONE FROM THE ROW STYLE, and that is the change rather than an omission. A tap
      connects and a long press ticks, so nothing on this screen means "selected" any more — a lit row would
      be a mark with no action behind it, sitting beside ticks that have one. What the list still keeps is
      SelectedItem, because the shared view model reads it: a tap assigns SelectedHost on the way to
      connecting, and the password sheet is about that host.
    -->
    <ListBox x:Name="Rows" Grid.Row="2" Margin="0,6,0,0" IsVisible="{Binding ShowsHostList}"
             ItemsSource="{Binding SidebarRows}"
             SelectedItem="{Binding SelectedSidebarRow}"
             Tapped="OnRowTapped"
             Background="Transparent" BorderThickness="0">

      <ListBox.Styles>
        <Style Selector="ListBoxItem">
          <Setter Property="Padding" Value="0" />
          <Setter Property="MinHeight" Value="0" />
          <Setter Property="Margin" Value="10,1" />
          <Setter Property="CornerRadius" Value="12" />
        </Style>

        <!--
          ◆ The ticked row, filled. On a Border inside the item rather than on the item's own selected state,
          because "ticked" is a property of the host and the list has no idea about it — the fill and the
          tick at the right-hand end are the same fact drawn twice, which is deliberate: a fill alone is
          what selection used to look like here, and a tick alone is a 14-pixel mark at the far edge of a
          360dp row.
        -->
        <Style Selector="Border.hostrow">
          <Setter Property="Background" Value="Transparent" />
          <Setter Property="CornerRadius" Value="12" />
        </Style>
        <Style Selector="Border.hostrow.chosen">
          <Setter Property="Background" Value="{StaticResource Active}" />
        </Style>
      </ListBox.Styles>

      <ListBox.DataTemplates>

        <!--
          A group heading. SidebarGroupHeader, not HostGroupRowViewModel — the latter is a row in the
          groups editor, and the two are deliberately different shapes: this one carries the count *after*
          the filter, which is what a heading over a filtered list has to say.
        -->
        <DataTemplate DataType="vm:SidebarGroupHeader">
          <Grid ColumnDefinitions="Auto,*,Auto,Auto" Margin="8,12,8,5">
            <TextBlock Grid.Column="0" Classes="detail" Text="{Binding Chevron}" VerticalAlignment="Center" />

            <!--
              The name, and the vault it is in where this session holds more than one. The badge is what
              makes one heading per group survive a shared vault: two vaults may each hold a "production",
              and this list has no nesting to tell the two apart with. Drawn as the same outline tag a host
              row wears, so "which vault" looks the same wherever it is answered.
            -->
            <StackPanel Grid.Column="1" Orientation="Horizontal" Spacing="6" Margin="7,0,0,0">
              <TextBlock Classes="section" Text="{Binding Label}" TextTrimming="CharacterEllipsis" />
              <Border Classes="tag outline" VerticalAlignment="Center"
                      IsVisible="{Binding HasVaultBadge}">
                <TextBlock Text="{Binding VaultBadge}" />
              </Border>
            </StackPanel>

            <TextBlock Grid.Column="2" Classes="detail" Text="{Binding Count}" FontSize="9"
                       VerticalAlignment="Center" />

            <!--
              ◆ The phone's only way to do anything to a group. The desktop reaches all three from a menu on
              the group's own card, which this head does not draw, and a heading's own selection deliberately
              bounces back to the host — so without this a group could be created here and never corrected,
              moved or removed. It matters more for a group than for a host: its defaults are what every host
              beneath it falls back to, and it is the one thing on this screen that can take hosts with it.

              It was the pencil alone, and became a menu when there were three things behind it rather than
              one. Three icons after a chevron, a name, a vault badge and a count is what would be left of the
              name at 360dp; see VaultViewModel.OpenGroupSheet.

              Hidden on the ungrouped heading, which has no group behind it to act on.
            -->
            <Button Grid.Column="3" Classes="icon" FontSize="15" Content="⋯" Margin="4,0,0,0"
                    IsVisible="{Binding GroupId, Converter={x:Static ObjectConverters.IsNotNull}}"
                    Command="{Binding $parent[views:HostsScreen].((vm:VaultViewModel)DataContext).OpenGroupSheetCommand}"
                    CommandParameter="{Binding}" />
          </Grid>
        </DataTemplate>

        <!-- A host. -->
        <DataTemplate DataType="vm:HostRowViewModel">
          <Border Classes="hostrow" Classes.chosen="{Binding IsChosen}">
            <Grid ColumnDefinitions="Auto,*,Auto,Auto" MinHeight="56" Margin="14,12">

              <!-- Green means a terminal is open on this host right now. See the .dot style. -->
              <Ellipse Grid.Column="0" Classes="dot" Classes.live="{Binding IsConnected}" Width="8" Height="8"
                       VerticalAlignment="Center" />

              <StackPanel Grid.Column="1" Margin="12,0,0,0" Spacing="3" VerticalAlignment="Center">
                <StackPanel Orientation="Horizontal" Spacing="7">
                  <TextBlock Classes="mono" Text="{Binding Label}" FontSize="14" FontWeight="SemiBold"
                             TextTrimming="CharacterEllipsis" />
                  <Border Classes="tag outline" IsVisible="{Binding HasVaultBadge}">
                    <TextBlock Text="{Binding VaultBadge}" />
                  </Border>
                  <Border Classes="tag outline" BorderBrush="{StaticResource WarnSoft}"
                          IsVisible="{Binding Badge, Converter={x:Static StringConverters.IsNotNullOrEmpty}}">
                    <TextBlock Foreground="{StaticResource Warn}" Text="{Binding Badge}" />
                  </Border>
                </StackPanel>
                <StackPanel Orientation="Horizontal" Spacing="6">
                  <TextBlock Classes="detail" FontSize="10.5" Text="{Binding Address}"
                             TextTrimming="CharacterEllipsis" />
                  <TextBlock Classes="detail" FontSize="10.5" Text="·" />
                  <TextBlock Classes="detail" FontSize="10.5" Text="{Binding Authentication}" />
                </StackPanel>

                <!--
                  The design's tag chips, and the row is where they were always meant to be — what a machine
                  *is*, under what it is called and where it answers. Wrapped rather than trimmed: a host
                  wearing four tags on a 360dp row takes a second line, which is the honest shape.
                -->
                <ItemsControl ItemsSource="{Binding TagLabels}" IsVisible="{Binding HasTags}"
                              Margin="0,2,0,0">
                  <ItemsControl.ItemsPanel>
                    <ItemsPanelTemplate><WrapPanel /></ItemsPanelTemplate>
                  </ItemsControl.ItemsPanel>
                  <ItemsControl.ItemTemplate>
                    <DataTemplate x:DataType="x:String">
                      <Border Classes="tag" Margin="0,0,4,3">
                        <TextBlock Text="{Binding}" />
                      </Border>
                    </DataTemplate>
                  </ItemsControl.ItemTemplate>
                </ItemsControl>
              </StackPanel>

              <!--
                The design's "live" tag, on exactly the hosts the dot is green for — and in the same green,
                overriding the tag style's accent text. A status word in the accent colour beside a green dot
                meaning the same thing would be one fact drawn in two colours.
              -->
              <Border Grid.Column="2" Classes="tag" IsVisible="{Binding IsConnected}"
                      Background="{StaticResource LiveWash}">
                <TextBlock Text="live" Foreground="{StaticResource Live}" />
              </Border>
              <TextBlock Grid.Column="2" Text="›" Foreground="{StaticResource TextGhost}" FontSize="15"
                         VerticalAlignment="Center" IsVisible="{Binding !IsConnected}" />

              <!--
                ◆ The tick, in a column of its own at the far end rather than in place of the dot or the
                chevron. Both of those say something a tick does not — whether a shell is open on this
                machine, and that the row leads somewhere — and swapping either for the tick would make
                choosing a host hide a fact about it. An Auto column takes no width while the mark is
                collapsed, so a list with nothing ticked is laid out exactly as it was.
              -->
              <TextBlock Grid.Column="3" Text="✓" Margin="10,0,0,0" FontSize="15"
                         Foreground="{StaticResource AccentText}" VerticalAlignment="Center"
                         IsVisible="{Binding IsChosen}" />
            </Grid>
          </Border>
        </DataTemplate>

      </ListBox.DataTemplates>
    </ListBox>

    <!-- ============ the group editor ============ -->
    <!--
      In the list's row and swapped for it, following SnippetsScreen. A group is enough of a thing to need
      one: a name, where it sits in the tree, and the three things hosts inside it fall back to. Every one of
      those may be left empty, and empty means the group lends nothing rather than that it has not been
      filled in.

      It stays a card in the list's row where the host editor became a page, and the difference is honest
      rather than an oversight: this form is seven controls and that one is twenty, and the host editor is
      the one the action bar's pencil opens — a control whose whole promise is that it leads somewhere with
      everything on it.

      The parent picker leaves out this group and everything beneath it, so a cycle cannot be made here. That
      is a courtesy rather than the guarantee — one assembled from two offline re-parents was never offered
      this list, which is why the walk itself stops at a repeat.
    -->
    <Border Grid.Row="2" Classes="card" Margin="12,0" IsVisible="{Binding IsEditingGroup}"
            VerticalAlignment="Top">
      <ScrollViewer MaxHeight="560">
        <StackPanel Spacing="10">
          <TextBlock Classes="label" Text="GROUP" />

          <TextBox Classes="field" Text="{Binding GroupEditorLabel}" PlaceholderText="group name" />

          <!--
            ◆ WHICH VAULT THIS GROUP WILL LIVE IN, on the same terms as the host editor's picker and for the
            same reason: it is what makes the group shared, and it cannot be changed afterwards. Above INSIDE
            because it decides what INSIDE can offer — a parent belongs to one vault, and one from another is
            a level half the readers cannot resolve.
          -->
          <StackPanel Spacing="6" IsVisible="{Binding ShowsGroupEditorVaultChoice}">
            <TextBlock Classes="label" Text="VAULT" Margin="0,4,0,0" />
            <ComboBox ItemsSource="{Binding GroupEditorVaultChoices}"
                      SelectedItem="{Binding GroupEditorSelectedVault}"
                      HorizontalAlignment="Stretch" MinHeight="44">
              <ComboBox.ItemTemplate>
                <DataTemplate x:DataType="vm:VaultChoiceViewModel">
                  <TextBlock Classes="mono" FontSize="12" Text="{Binding Display}" />
                </DataTemplate>
              </ComboBox.ItemTemplate>
            </ComboBox>
            <TextBlock Classes="body"
                       Text="A group in a shared vault is visible to everybody holding that vault's key, and only hosts in the same vault can be filed under it." />
          </StackPanel>

          <TextBlock Classes="label" Text="INSIDE" Margin="0,4,0,0" />
          <ComboBox ItemsSource="{Binding GroupEditorParentChoices}"
                    SelectedItem="{Binding GroupEditorSelectedParent}"
                    HorizontalAlignment="Stretch" MinHeight="44">
            <ComboBox.ItemTemplate>
              <DataTemplate x:DataType="vm:GroupChoice">
                <TextBlock Classes="mono" FontSize="12" Text="{Binding Label}" />
              </DataTemplate>
            </ComboBox.ItemTemplate>
          </ComboBox>

          <TextBlock Classes="label" Text="HOSTS INSIDE FALL BACK TO" Margin="0,6,0,0" />

          <Grid ColumnDefinitions="*,8,2*">
            <NumericUpDown Grid.Column="0" Value="{Binding GroupEditorDefaultPort}" Minimum="1"
                           Maximum="65535" FormatString="0" ShowButtonSpinner="False" MinHeight="44"
                           PlaceholderText="no default" />
            <TextBox Grid.Column="2" Classes="field" Text="{Binding GroupEditorDefaultUsername}"
                     PlaceholderText="no default user" />
          </Grid>

          <ComboBox ItemsSource="{Binding GroupEditorAuthenticationChoices}"
                    SelectedItem="{Binding GroupEditorSelectedAuthentication}"
                    HorizontalAlignment="Stretch" MinHeight="44">
            <ComboBox.ItemTemplate>
              <DataTemplate x:DataType="vm:AuthenticationChoice">
                <StackPanel Orientation="Horizontal" Spacing="6">
                  <TextBlock Classes="mono" FontSize="12" Text="{Binding Label}" />
                  <TextBlock Classes="detail" FontSize="10" VerticalAlignment="Center"
                             Text="{Binding Qualifier}"
                             IsVisible="{Binding Qualifier, Converter={x:Static StringConverters.IsNotNullOrEmpty}}" />
                </StackPanel>
              </DataTemplate>
            </ComboBox.ItemTemplate>
          </ComboBox>

          <TextBlock Classes="body"
                     Text="A host inside this group uses these wherever it says nothing itself. They are read at connect time rather than copied in, so changing one here changes every host that never overrode it." />

          <Grid ColumnDefinitions="*,8,*">
            <Button Grid.Column="0" Classes="primary" Height="44" Content="SAVE"
                    Command="{Binding SaveGroupCommand}" />
            <Button Grid.Column="2" Classes="secondary" Height="44" Content="CANCEL"
                    Command="{Binding CancelGroupEditCommand}" />
          </Grid>
        </StackPanel>
      </ScrollViewer>
    </Border>

    <!-- ============ add ============ -->
    <!--
      ◆ The design's floating +, and the reason every version of this file until v3 said it could not be
      drawn.

      <b>Grid.Row 2, inside the Grid — not the Panel around it.</b> A Panel child aligned to the bottom is
      anchored to the bottom of the screen and would sit over whatever the list's own row holds. In the
      list's row it rides with the list, which is what a floating button does on every other Android
      application and what a thumb reaches for.

      Hidden rather than disabled while anything is open over the list, and hidden while hosts are ticked. A
      + that opens an editor on top of an open editor is a control with nothing to do; a + offering to add a
      twelfth machine while eleven are ticked for deletion is worse, because it does something and the
      something is not what the screen is about.
    -->
    <Button Grid.Row="2" Classes="fab" Content="+" Margin="0,0,18,18"
            HorizontalAlignment="Right" VerticalAlignment="Bottom"
            IsVisible="{Binding ShowsAddButton}"
            Command="{Binding OpenAddSheetCommand}" />

  </Grid>

    <!-- ============ the add sheet ============ -->
    <!--
      Follows HostKeySheet, this head's one existing sheet: a scrim, a bottom-aligned panel with the top two
      corners rounded, and a grab handle that is decoration. Two things it does differently, both because it
      is a menu rather than a decision.

      It is dismissible. Tapping the scrim closes it, which HostKeySheet deliberately refuses — answering
      "do you trust this key" by tapping beside it is the swipe-to-dismiss that head was built to avoid.
      "Which kind of thing are you adding" has no wrong answer and no answer at all is one of them.

      And it lives here rather than in PhoneShell. HostKeySheet sits up there because the files screen raises
      it too; nothing but this screen raises this. The cost is that the scrim covers the screen and not the
      64px nav bar underneath, so the four destinations stay lit and tappable — which for a menu is right:
      navigating away is a perfectly good way to decide not to add anything.
    -->
    <Panel IsVisible="{Binding IsAddSheetOpen}">

      <!--
        Declared before the sheet so the sheet draws over it: a Panel stacks its children in declaration
        order. See the .scrim style for why a tap on it must not light anything up.
      -->
      <Button Classes="scrim" Command="{Binding CloseAddSheetCommand}" />

      <Border VerticalAlignment="Bottom" Background="{StaticResource Panel}"
              BorderBrush="{StaticResource BorderMid}" BorderThickness="0,1,0,0"
              CornerRadius="22,22,0,0" Padding="20,18,20,16">
        <StackPanel Spacing="0">

          <Border Width="38" Height="4" CornerRadius="2" Background="{StaticResource BorderMid}"
                  HorizontalAlignment="Center" Margin="0,0,0,16" />

          <TextBlock Classes="title" Text="ADD" FontSize="13" />

          <Button Classes="row" Margin="0,10,0,0" Command="{Binding NewHostCommand}">
            <StackPanel Spacing="3" VerticalAlignment="Center">
              <TextBlock Classes="mono" FontSize="13.5" FontWeight="SemiBold" Text="New host" />
              <TextBlock Classes="detail" FontSize="10.5"
                         Text="A machine to connect to, filed under a group if you want one." />
            </StackPanel>
          </Button>

          <Button Classes="row" Command="{Binding NewGroupCommand}">
            <StackPanel Spacing="3" VerticalAlignment="Center">
              <TextBlock Classes="mono" FontSize="13.5" FontWeight="SemiBold" Text="New group" />
              <TextBlock Classes="detail" FontSize="10.5"
                         Text="A heading for hosts, and the port, user and key they fall back to." />
            </StackPanel>
          </Button>

          <Button Classes="secondary" Content="CANCEL" Margin="0,12,0,0"
                  Command="{Binding CloseAddSheetCommand}" />

        </StackPanel>
      </Border>
    </Panel>

    <!-- ============ ◆ the host action sheet ============ -->
    <!--
      THE SEVEN THINGS BEHIND THE ⋯ IN THE ACTION BAR, and the whole of what the connect card used to be
      plus three things it could not express.

      A sheet rather than a flyout hanging off the button that raised it. That button is in the top-right
      corner of a 360dp screen and the hand holding the phone is at the bottom of it; a flyout would put
      seven entries where the thumb is not, and would have no room for the sentence three of them need. This
      is the add sheet's shape doing the same job the group sheet already does.

      Dismissible, on the add sheet's terms rather than the host key sheet's: "which of these seven" has no
      wrong answer, and none of them is a perfectly good outcome.

      ── which entries are drawn ─────────────────────────────────────────────────────────────────────────
      The first two are collapsed unless exactly one host is ticked. A terminal and a file-transfer session
      are each a connection somebody is about to look at, and six of either is five nobody asked for — see
      VaultViewModel.HasOneChosenHost. The remaining four act on the whole set and read better for it: it is
      the reason the set exists.

      Delete is below a rule, which is the same separator the group sheet draws and means the same thing: a
      phone has no hover and no tooltip, so where the thumb lands is the only thing between a destructive
      entry and an ordinary one.
    -->
    <Panel IsVisible="{Binding IsHostActionSheetOpen}">

      <Button Classes="scrim" Command="{Binding CloseHostActionSheetCommand}" />

      <Border VerticalAlignment="Bottom" Background="{StaticResource Panel}"
              BorderBrush="{StaticResource BorderMid}" BorderThickness="0,1,0,0"
              CornerRadius="22,22,0,0" Padding="20,18,20,16">
        <StackPanel Spacing="0">

          <Border Width="38" Height="4" CornerRadius="2" Background="{StaticResource BorderMid}"
                  HorizontalAlignment="Center" Margin="0,0,0,16" />

          <StackPanel Orientation="Horizontal" Spacing="8">
            <TextBlock Classes="title" Text="HOSTS" FontSize="13" />
            <TextBlock Classes="mono" FontSize="12" VerticalAlignment="Center"
                       Text="{Binding ChosenHostsLabel}" />
          </StackPanel>

          <!--
            Scrolled rather than trusted to fit. Seven entries, three of them two lines, is taller than a
            small phone in landscape — and a sheet whose last entry is the destructive one must not be a
            sheet whose last entry falls off the bottom.
          -->
          <ScrollViewer MaxHeight="440" Margin="0,10,0,0">
            <StackPanel Spacing="0">

              <Button Classes="row" IsVisible="{Binding HasOneChosenHost}"
                      Command="{Binding ConnectToChosenHostCommand}">
                <TextBlock Classes="mono" FontSize="13.5" FontWeight="SemiBold" VerticalAlignment="Center"
                           Text="Connect" />
              </Button>

              <Button Classes="row" IsVisible="{Binding HasOneChosenHost}"
                      Command="{Binding BrowseChosenHostCommand}">
                <TextBlock Classes="mono" FontSize="13.5" FontWeight="SemiBold" VerticalAlignment="Center"
                           Text="Connect via SFTP" />
              </Button>

              <Button Classes="row" Command="{Binding MoveChosenHostsToVaultCommand}">
                <StackPanel Spacing="3" VerticalAlignment="Center">
                  <TextBlock Classes="mono" FontSize="13.5" FontWeight="SemiBold" Text="Move to vault" />
                  <TextBlock Classes="detail" FontSize="10.5" TextWrapping="Wrap"
                             Text="Re-encrypted with the other vault's key. Groups and tags stay behind." />
                </StackPanel>
              </Button>

              <Button Classes="row" Command="{Binding CopyChosenHostsToVaultCommand}">
                <StackPanel Spacing="3" VerticalAlignment="Center">
                  <TextBlock Classes="mono" FontSize="13.5" FontWeight="SemiBold" Text="Copy to vault" />
                  <TextBlock Classes="detail" FontSize="10.5" TextWrapping="Wrap"
                             Text="A second copy over there, and these stay where they are." />
                </StackPanel>
              </Button>

              <Button Classes="row" Command="{Binding RegroupChosenHostsCommand}">
                <TextBlock Classes="mono" FontSize="13.5" FontWeight="SemiBold" VerticalAlignment="Center"
                           Text="Change group" />
              </Button>

              <Button Classes="row" Command="{Binding DuplicateChosenHostsCommand}">
                <TextBlock Classes="mono" FontSize="13.5" FontWeight="SemiBold" VerticalAlignment="Center"
                           Text="Duplicate" />
              </Button>

              <Border Height="1" Background="{StaticResource Border}" Margin="0,8" />

              <Button Classes="row" Command="{Binding DeleteChosenHostsCommand}">
                <StackPanel Spacing="3" VerticalAlignment="Center">
                  <TextBlock Classes="mono" FontSize="13.5" FontWeight="SemiBold"
                             Foreground="{StaticResource Danger}" Text="Remove" />
                  <TextBlock Classes="detail" FontSize="10.5" TextWrapping="Wrap"
                             Text="Asks first, and says how many. There is no undo." />
                </StackPanel>
              </Button>

            </StackPanel>
          </ScrollViewer>

          <Button Classes="secondary" Content="CANCEL" Margin="0,12,0,0"
                  Command="{Binding CloseHostActionSheetCommand}" />

        </StackPanel>
      </Border>
    </Panel>

    <!-- ============ ◆ the password sheet ============ -->
    <!--
      WHAT IS LEFT OF THE CONNECT CARD, and the only part of it that had nowhere else to go.

      A host that authenticates with a typed password cannot be reached by a tap alone: there is nothing on
      the row to type into. So the tap that cannot finish raises this, rather than doing nothing or — the one
      outcome that must never happen — dialling with no password. See VaultViewModel.ConnectToRowAsync, where
      the branch lives, and IsAskingForConnectPassword, which is this sheet.

      The difference from the bar it replaces is that this is up only while a question is on screen. The bar
      was raised by a long press and stayed, so it was a password box sitting over the list whether or not
      anything was being asked.

      Dismissible, and dismissing empties the box: a password left behind would be somebody else's secret in
      the box the next tap reads, and it would satisfy the length check that decides whether to raise this at
      all. See CancelConnectPassword.
    -->
    <Panel IsVisible="{Binding IsAskingForConnectPassword}">

      <Button Classes="scrim" Command="{Binding CancelConnectPasswordCommand}" />

      <Border VerticalAlignment="Bottom" Background="{StaticResource Panel}"
              BorderBrush="{StaticResource BorderMid}" BorderThickness="0,1,0,0"
              CornerRadius="22,22,0,0" Padding="20,18,20,16">
        <StackPanel Spacing="10">

          <Border Width="38" Height="4" CornerRadius="2" Background="{StaticResource BorderMid}"
                  HorizontalAlignment="Center" Margin="0,0,0,6" />

          <!--
            It names the host, as the bar did and for the same reason: the row it was tapped on is behind a
            sheet and a scrim, and "which machine is this password for" is the one thing somebody about to
            type a secret has to be sure of.
          -->
          <StackPanel Orientation="Horizontal" Spacing="8">
            <TextBlock Classes="label" Text="CONNECT TO" />
            <TextBlock Classes="mono" FontSize="11" VerticalAlignment="Center"
                       Text="{Binding SelectedHost.Label}" TextTrimming="CharacterEllipsis" />
          </StackPanel>

          <TextBox Classes="field secret" Text="{Binding ConnectPassword}" PlaceholderText="password">
            <TextBox.KeyBindings>
              <KeyBinding Gesture="Enter" Command="{Binding ConnectCommand}" />
            </TextBox.KeyBindings>
          </TextBox>

          <!--
            The tick is the phone's whole answer to storing a password, and on this head it is the only one:
            the keychain lists credentials here but has no editor to create one in, so before it a password
            typed on a phone could only ever be typed again. What it produces is an ordinary credential bound
            to this host — see VaultViewModel.RemembersConnectPassword — rather than a fourth place a secret
            quietly lives.
          -->
          <CheckBox IsChecked="{Binding RemembersConnectPassword}" MinHeight="44">
            <TextBlock Classes="mono" FontSize="11.5" TextWrapping="Wrap"
                       Text="Remember this password for this host" />
          </CheckBox>

          <Grid ColumnDefinitions="*,8,*">
            <Button Grid.Column="0" Classes="primary" Height="44" Content="CONNECT"
                    Command="{Binding ConnectCommand}" IsEnabled="{Binding !IsBusy}" />
            <Button Grid.Column="2" Classes="secondary" Height="44" Content="CANCEL"
                    Command="{Binding CancelConnectPasswordCommand}" />
          </Grid>

        </StackPanel>
      </Border>
    </Panel>

    <!-- ============ ◆ the group sheet ============ -->
    <!--
      The three things that can be done to a group, raised from the ⋯ on its heading. The desktop draws the
      same three as a right-click menu on the group's card; this head has no card and no right-click, so it
      is the add sheet's shape doing the same job — a scrim, a bottom panel with the top two corners rounded,
      and a grab handle that is decoration.

      Dismissible, on the add sheet's terms and not the host key sheet's: "which of these three" has no wrong
      answer and none of them is a perfectly good outcome.

      It names the group in its own title, which the desktop's menu does not have to — a right-click menu
      opens over the card it is about, and this opens over a list the heading may have scrolled out of. Two
      vaults may each hold a "production", so the badge on the heading is the other half of that answer.

      ◆ Delete is below a rule and Move is above it, which is the desktop's own order and its own separator:
      moving is not a field of the group and it is not a deletion either. The two vaults are encrypted under
      different keys, so a move is a re-seal of the whole shelf into one and a tombstone per item in the
      other — the entry says so rather than reading as a filing gesture.

      Open is the entry the desktop has and this does not. Its grid holds one level of the tree at a time and
      this list holds all of it flattened, so there is nothing here to open a group into.
    -->
    <Panel IsVisible="{Binding GroupSheet, Converter={x:Static ObjectConverters.IsNotNull}}">

      <Button Classes="scrim" Command="{Binding CloseGroupSheetCommand}" />

      <Border VerticalAlignment="Bottom" Background="{StaticResource Panel}"
              BorderBrush="{StaticResource BorderMid}" BorderThickness="0,1,0,0"
              CornerRadius="22,22,0,0" Padding="20,18,20,16">
        <StackPanel Spacing="0">

          <Border Width="38" Height="4" CornerRadius="2" Background="{StaticResource BorderMid}"
                  HorizontalAlignment="Center" Margin="0,0,0,16" />

          <StackPanel Orientation="Horizontal" Spacing="8">
            <TextBlock Classes="title" Text="GROUP" FontSize="13" />
            <TextBlock Classes="mono" FontSize="12" Text="{Binding GroupSheetLabel}"
                       TextTrimming="CharacterEllipsis" VerticalAlignment="Center" />
          </StackPanel>

          <Button Classes="row" Margin="0,10,0,0"
                  Command="{Binding EditGroupFromHeadingCommand}"
                  CommandParameter="{Binding GroupSheet}">
            <StackPanel Spacing="3" VerticalAlignment="Center">
              <TextBlock Classes="mono" FontSize="13.5" FontWeight="SemiBold" Text="Edit" />
              <TextBlock Classes="detail" FontSize="10.5"
                         Text="Its name, where it sits, and the port, user and key its hosts fall back to." />
            </StackPanel>
          </Button>

          <Button Classes="row"
                  Command="{Binding MoveGroupFromHeadingCommand}"
                  CommandParameter="{Binding GroupSheet}">
            <StackPanel Spacing="3" VerticalAlignment="Center">
              <TextBlock Classes="mono" FontSize="13.5" FontWeight="SemiBold"
                         Text="Move to another vault" />
              <TextBlock Classes="detail" FontSize="10.5" TextWrapping="Wrap"
                         Text="Takes the groups inside it and every host filed under them, re-encrypted with the other vault's key." />
            </StackPanel>
          </Button>

          <Border Height="1" Background="{StaticResource Border}" Margin="0,8" />

          <Button Classes="row"
                  Command="{Binding DeleteGroupFromHeadingCommand}"
                  CommandParameter="{Binding GroupSheet}">
            <StackPanel Spacing="3" VerticalAlignment="Center">
              <TextBlock Classes="mono" FontSize="13.5" FontWeight="SemiBold"
                         Foreground="{StaticResource Danger}" Text="Delete" />
              <TextBlock Classes="detail" FontSize="10.5" TextWrapping="Wrap"
                         Text="Asks first, and asks separately whether the hosts filed under it go too." />
            </StackPanel>
          </Button>

          <Button Classes="secondary" Content="CANCEL" Margin="0,12,0,0"
                  Command="{Binding CloseGroupSheetCommand}" />

        </StackPanel>
      </Border>
    </Panel>

    <!-- ============ ◆ the host editor, as a page ============ -->
    <!--
      A PAGE OVER THE WHOLE SCREEN, WHICH IS THE OTHER HALF OF THE PENCIL MOVING INTO THE ACTION BAR.

      It was a card in the list's own row, under the search box and the sync line — a form with twenty
      controls in it sharing a screen with two rows of chrome about the list it had replaced. Declared last
      in this Panel so it draws over everything, opaque so nothing shows through, and PhoneShell stands its
      own four rows down while it is up: no vault header, no shells strip, no bottom bar, no rail. See
      PhoneShell.RefreshChrome.

      Its header carries what the surrounding chrome no longer can: the way back, what this is, and SAVE. The
      arrow cancels rather than saving, which is what CANCEL below the form does and what the system back
      gesture does — three ways out with one meaning between them.

      Every field is the shared view model's — the same properties the desktop's drawer binds — so the two
      heads cannot drift about what a host is or what saving one does.

      ◆ The port and username boxes may be left empty, and empty is not "unset": it means the host takes its
      group's. The watermark on each is what it will actually get, and it follows the group picker below as
      that moves, so the form says what leaving a box blank will do rather than making it a guess. See
      HostInheritance.
    -->
    <Grid IsVisible="{Binding IsEditing}" RowDefinitions="Auto,*"
          Background="{StaticResource Canvas}">

      <!-- DeepChrome rather than Chrome, since v5: "its header carries what the surrounding chrome no longer
           can" below is still true, and now the header itself carries the darker chrome the rest of this
           head's frame does — see the remark on PhoneShell's vault header. -->
      <Border Grid.Row="0" Background="{StaticResource DeepChrome}" BorderBrush="{StaticResource Border}"
              BorderThickness="0,0,0,1" Padding="6,0" Height="56">
        <Grid ColumnDefinitions="Auto,*,Auto">
          <Button Grid.Column="0" Classes="icon" Content="←" Command="{Binding CancelEditCommand}"
                  ToolTip.Tip="Leave without saving" />
          <TextBlock Grid.Column="1" Classes="heading" FontSize="16" Margin="4,0"
                     VerticalAlignment="Center" Text="{Binding DrawerTitle}"
                     TextTrimming="CharacterEllipsis" />
          <Button Grid.Column="2" Classes="icon accent" Content="✓" FontSize="17"
                  Command="{Binding SaveHostCommand}" ToolTip.Tip="Save this host" />
        </Grid>
      </Border>

      <ScrollViewer Grid.Row="1">
        <StackPanel Spacing="10" Margin="16,14,16,24">
          <TextBlock Classes="label" Text="HOST" />

          <TextBox Classes="field" Text="{Binding EditorLabel}" PlaceholderText="name" />
          <TextBox Classes="field" Text="{Binding EditorHostname}" PlaceholderText="hostname or address" />

          <Grid ColumnDefinitions="*,8,2*">
            <NumericUpDown Grid.Column="0" Value="{Binding EditorPort}" Minimum="1" Maximum="65535"
                           FormatString="0" ShowButtonSpinner="False" MinHeight="44"
                           PlaceholderText="{Binding EditorPortPlaceholder}" />
            <TextBox Grid.Column="2" Classes="field" Text="{Binding EditorUsername}"
                     PlaceholderText="{Binding EditorUsernamePlaceholder}" />
          </Grid>

          <TextBox Classes="field" Text="{Binding EditorNotes}" PlaceholderText="notes" />

          <!--
            One picker for all of them, as on the desktop, which is what makes "a key or a credential, never
            both" impossible to express rather than merely invalid. The qualifier beside each label matters
            for the same reason it does there: a key called deploy and the deploy account's password are the
            ordinary case, and bare labels would offer two identical-looking entries.
          -->
          <TextBlock Classes="label" Text="AUTHENTICATION" Margin="0,4,0,0" />
          <ComboBox ItemsSource="{Binding EditorAuthenticationChoices}"
                    SelectedItem="{Binding EditorSelectedAuthentication}"
                    HorizontalAlignment="Stretch" MinHeight="44">
            <ComboBox.ItemTemplate>
              <DataTemplate x:DataType="vm:AuthenticationChoice">
                <StackPanel Orientation="Horizontal" Spacing="6">
                  <TextBlock Classes="mono" FontSize="12" Text="{Binding Label}" />
                  <TextBlock Classes="detail" FontSize="10" VerticalAlignment="Center"
                             Text="{Binding Qualifier}"
                             IsVisible="{Binding Qualifier, Converter={x:Static StringConverters.IsNotNullOrEmpty}}" />
                </StackPanel>
              </DataTemplate>
            </ComboBox.ItemTemplate>
          </ComboBox>

          <!--
            ◆ WHICH VAULT THIS HOST WILL LIVE IN. Drawn only while adding and only where there is more than
            one vault that can be written to, exactly as on the desktop — an existing host's vault is not a
            field of this form, because the two are encrypted under different keys and changing it is a
            re-seal into one vault and a tombstone in the other. That is "Move to vault" in the action bar's
            menu, and it is separate so that it cannot happen as a side effect of saving something else.
            Above GROUP rather than below it because it decides what GROUP can offer: a group is an item in
            one vault, so choosing a vault refills that list with that vault's groups.
          -->
          <StackPanel Spacing="6" IsVisible="{Binding ShowsEditorVaultChoice}">
            <TextBlock Classes="label" Text="VAULT" Margin="0,4,0,0" />
            <ComboBox ItemsSource="{Binding EditorVaultChoices}"
                      SelectedItem="{Binding EditorSelectedVault}"
                      HorizontalAlignment="Stretch" MinHeight="44">
              <ComboBox.ItemTemplate>
                <DataTemplate x:DataType="vm:VaultChoiceViewModel">
                  <TextBlock Classes="mono" FontSize="12" Text="{Binding Display}" />
                </DataTemplate>
              </ComboBox.ItemTemplate>
            </ComboBox>
            <TextBlock Classes="body"
                       Text="A host in a shared vault is readable by everybody holding that vault's key. It can be moved out later, from the action bar's menu — what it cannot do is become unreadable to somebody who has already synced it." />
          </StackPanel>

          <TextBlock Classes="label" Text="GROUP" Margin="0,4,0,0" />
          <ComboBox ItemsSource="{Binding EditorGroupChoices}"
                    SelectedItem="{Binding EditorSelectedGroup}"
                    HorizontalAlignment="Stretch" MinHeight="44">
            <ComboBox.ItemTemplate>
              <DataTemplate x:DataType="vm:GroupChoice">
                <TextBlock Classes="mono" FontSize="12" Text="{Binding Label}" />
              </DataTemplate>
            </ComboBox.ItemTemplate>
          </ComboBox>

          <!--
            ◆ The tags this host wears. Chips that toggle, matching the chips on the row behind this page —
            a list of names to tick would make the user match an entry to a chip they can see.

            The box under them creates one and puts it on straight away, which is where a tag is usually
            wanted: while tagging a host and finding it does not exist yet. It writes to the keychain
            immediately, unlike every other field here, because a host can only name a tag that has an id —
            so cancelling this editor leaves the tag behind. Renaming and deleting are on the keychain
            screen, with every other item kind.
          -->
          <TextBlock Classes="label" Text="TAGS" Margin="0,4,0,0" />

          <ItemsControl ItemsSource="{Binding EditorTagChoices}" IsVisible="{Binding HasTagChoices}">
            <ItemsControl.ItemsPanel>
              <ItemsPanelTemplate><WrapPanel /></ItemsPanelTemplate>
            </ItemsControl.ItemsPanel>
            <ItemsControl.ItemTemplate>
              <DataTemplate x:DataType="vm:TagChoice">
                <Button Classes="chiptoggle" Classes.worn="{Binding IsWorn}" Margin="0,0,6,6"
                        Command="{Binding $parent[ItemsControl].((vm:VaultViewModel)DataContext).ToggleEditorTagCommand}"
                        CommandParameter="{Binding}">
                  <TextBlock Classes="mono" FontSize="11.5" Text="{Binding Label}" />
                </Button>
              </DataTemplate>
            </ItemsControl.ItemTemplate>
          </ItemsControl>

          <Grid ColumnDefinitions="*,8,Auto">
            <TextBox Grid.Column="0" Classes="field" Text="{Binding EditorNewTag}"
                     PlaceholderText="new tag" />
            <Button Grid.Column="2" Classes="secondary" Height="44" Width="72" Content="ADD"
                    Command="{Binding AddEditorTagCommand}" />
          </Grid>

          <!--
            ◆ QUICK ACCESS. Staged on the shared VaultViewModel.EditorPinnedPaths the way EditorTagChoices
            stages tags above — populated when the editor opens, read back by BuildHost on SAVE, left alone
            by CANCEL. Nothing here is phone-only: a pin added on this page is the same PinnedPathList entry
            the desktop's drawer stages under the same field, so it syncs to every other client the moment
            this vault does, exactly as a tag would.

            No folder glyph in the row, unlike the desktop's — this head carries MonoFont and a heading font
            (see Theme/Phone.axaml) and no icon font, so the desktop's &#xE2C7; would draw as a tofu box
            rather than a folder here. The path text carries the row on its own, trimmed in a Grid's star
            column rather than a StackPanel's — a StackPanel measures its children at infinity, so
            TextTrimming never actually engages inside one; this is the same fix TransfersScreen's pane
            headers needed for the same reason.

            The remove button is Classes="icon" at its default 44x44, not the desktop's 22x22 close box —
            see this file's touch-floor rule at the top of the class: a target sized for a mouse pointer is
            not one a thumb can reliably land on.

            See surface B, the Files screen's pin chips, for where these pins actually get used on this
            head — the hint sentence below names it rather than the desktop's terminal strip, which does
            not exist here.
          -->
          <TextBlock Classes="label" Text="QUICK ACCESS" Margin="0,4,0,0" />

          <ItemsControl ItemsSource="{Binding EditorPinnedPaths}">
            <ItemsControl.ItemsPanel>
              <ItemsPanelTemplate><StackPanel Spacing="6" /></ItemsPanelTemplate>
            </ItemsControl.ItemsPanel>
            <ItemsControl.ItemTemplate>
              <DataTemplate x:DataType="x:String">
                <Border MinHeight="44" CornerRadius="10" Background="{StaticResource Field}"
                        BorderBrush="{StaticResource BorderMid}" BorderThickness="1" Padding="12,0">
                  <Grid ColumnDefinitions="*,Auto">
                    <TextBlock Grid.Column="0" Classes="mono" FontSize="12.5" Text="{Binding}"
                               VerticalAlignment="Center" TextTrimming="CharacterEllipsis" />
                    <Button Grid.Column="1" Classes="icon" Content="✕"
                            Command="{Binding $parent[ItemsControl].((vm:VaultViewModel)DataContext).RemoveEditorPinCommand}"
                            CommandParameter="{Binding}" ToolTip.Tip="Unpins this path" />
                  </Grid>
                </Border>
              </DataTemplate>
            </ItemsControl.ItemTemplate>
          </ItemsControl>

          <Grid ColumnDefinitions="*,8,Auto">
            <TextBox Grid.Column="0" Classes="field" Text="{Binding EditorNewPin}"
                     PlaceholderText="/path/to/folder" />
            <Button Grid.Column="2" Classes="secondary" Height="44" Width="72" Content="ADD"
                    Command="{Binding AddEditorPinCommand}" />
          </Grid>

          <TextBlock Classes="body"
                     Text="Pins show up as chips on the Files screen while you are connected to this host — tap one to jump straight there." />

          <!--
            ◆ The one control here that publishes something. Turning it on copies this host's address and
            port into plaintext columns the server can read, which is the single deliberate concession in
            the design — see ADR 0004 — so it says so rather than being a switch labelled "relay".

            ◆ AND IT SAYS, FIRST, THAT IT BUYS NOTHING TODAY. The relay's server half is built and this
            client has no path to it — the connect path dials the address directly whether this is ticked or
            not — so the box as it stood spent that concession and delivered nothing for it. Left tickable
            rather than disabled, so a host already carrying the flag can lose it. See
            docs/reaching-a-host-you-cannot-dial.md.
          -->
          <CheckBox IsChecked="{Binding EditorRelayEnabled}" MinHeight="44">
            <TextBlock Classes="mono" FontSize="11.5"
                       Text="Reach this host through the server relay (not wired up yet)"
                       TextWrapping="Wrap" />
          </CheckBox>
          <TextBlock Classes="body"
                     Text="Not built yet: this app always dials the host itself, so ticking this stores the address on the server and changes nothing about how the host is reached. When it works, the relay will dial on your behalf — which is why the address and port have to be stored in the clear. Everything else about the host stays encrypted either way, and a relayed host needs a port of its own rather than its group's." />

          <!--
            This page covers the whole screen while it is open, which is why it needs a Status line of its
            own rather than relying on the one the list behind it draws at the top of HOSTS (see this file's
            other Classes="detail" Text="{Binding Status}") — that TextBlock is off-screen for as long as
            IsEditing is true. AddEditorTagCommand and AddEditorPinCommand both refuse silently into Status
            rather than throwing, so without a line to draw it here the honesty rule would be broken: a
            refusal that writes a message nothing on screen shows is the same, to whoever pressed ADD, as no
            refusal at all.
          -->
          <TextBlock Classes="detail" Text="{Binding Status}" TextWrapping="Wrap"
                     IsVisible="{Binding Status, Converter={x:Static StringConverters.IsNotNullOrEmpty}}" />

          <Grid ColumnDefinitions="*,8,*" Margin="0,6,0,0">
            <Button Grid.Column="0" Classes="primary" Height="44" Content="SAVE"
                    Command="{Binding SaveHostCommand}" />
            <Button Grid.Column="2" Classes="secondary" Height="44" Content="CANCEL"
                    Command="{Binding CancelEditCommand}" />
          </Grid>
        </StackPanel>
      </ScrollViewer>
    </Grid>

  </Panel>

</UserControl>
