Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Added

- **The flight log grows with the window, and its text can be copied or saved.** Drag Fido taller and
every spare pixel now goes to the **flight log** instead of to a gap above it — the upper section
keeps the room its content needs and the log takes the rest; shrink the window and the log falls back
to its compact box while the upper section scrolls as before. Two new buttons on the **Flight log**
rule lift the narration out: **copy** puts the whole log (every line, not just the visible ones) on
the clipboard as plain text, and **save** writes it to a text file you pick, suggesting a dated name
like `fido-flight-log-20260806-142317.txt`. Both are disabled until there's something to hand over,
and both confirm themselves in the log.

- **Copy the selected working-tree path to the clipboard.** The OPEN strip now has a small **copy
button** beside the path, and the ellipsised card and strip paths carry a **tooltip with the full
path** — so a long worktree path (previously truncated and un-selectable) can be read in full and
Expand Down
15 changes: 15 additions & 0 deletions Docs/Features.md
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,21 @@ green `✓` for successes, plain `▸` for actions — and failures call it stra
lines for a branch that isn't checked out anywhere, a tool that can't be located, or a
delete that went wrong.

The panel **grows with the window**: drag Fido's bottom edge down and every spare pixel
goes to the log rather than to a gap above it — the upper section keeps as much room as
its content needs, and the log takes the rest. Shrink the window again and the log falls
back to its compact box while the upper section scrolls.

Two buttons on the **Flight log** rule take the narration with you:

- **Copy** puts the whole log — every line, not just the visible ones — on the clipboard
as plain text.
- **Save** writes it to a text file you pick, suggesting a dated name like
`fido-flight-log-20260806-142317.txt`.

Both are disabled until there's something to hand over, and each confirms itself in the
log (`📋 Copied 8 flight-log line(s) to the clipboard.`, `✓ Flight log saved to …`).

### Keyboard & shortcuts

- The **branch** field is focused on launch. Typing debounces into a scan; **Enter**
Expand Down
26 changes: 26 additions & 0 deletions src/Services/AvaloniaDialogService.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Avalonia.Controls;
using Avalonia.Platform.Storage;
using Fido.Models;
using Fido.Views;

Expand All @@ -16,4 +17,29 @@ public Task<bool> ConfirmForceDeleteWorktreeFolderAsync(WorktreeForceDelete requ

public Task ShowSettingsAsync(AppConfig config, ConfigService configService)
=> new SettingsDialog(config, configService).ShowDialog(_owner);

public async Task<string?> PickFlightLogPathAsync(string suggestedFileName)
{
var storage = _owner.StorageProvider;
if (!storage.CanSave) return null;

var file = await storage.SaveFilePickerAsync(new FilePickerSaveOptions
{
Title = "Save flight log",
SuggestedFileName = suggestedFileName,
DefaultExtension = "txt",
ShowOverwritePrompt = true,
FileTypeChoices =
[
new FilePickerFileType("Text file")
{
Patterns = ["*.txt"],
MimeTypes = ["text/plain"],
},
],
});

// A picked file is written by path — Fido isn't sandboxed, and the log is plain text.
return file?.TryGetLocalPath();
}
}
6 changes: 6 additions & 0 deletions src/Services/IDialogService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,10 @@ public interface IDialogService

/// <summary>Opens the settings dialog (modal).</summary>
Task ShowSettingsAsync(AppConfig config, ConfigService configService);

/// <summary>
/// Asks where to save the flight log, offering <paramref name="suggestedFileName"/>. Returns the
/// chosen path, or null when the user cancelled (or the platform has no save picker).
/// </summary>
Task<string?> PickFlightLogPathAsync(string suggestedFileName);
}
21 changes: 14 additions & 7 deletions src/Theme/FidoStyles.axaml
Original file line number Diff line number Diff line change
Expand Up @@ -566,32 +566,39 @@
<Setter Property="Foreground" Value="{DynamicResource FidoAccentStrong}" />
</Style>

<!-- Copy-to-clipboard icon button (next to the selected path in the OPEN strip) -->
<Style Selector="Button.copypath">
<!-- Small ghost icon button: copy-to-clipboard beside the selected path, copy/save on the flight log -->
<Style Selector="Button.iconaction">
<Setter Property="Background" Value="Transparent" />
<Setter Property="BorderThickness" Value="0" />
<Setter Property="Padding" Value="5" />
<Setter Property="CornerRadius" Value="6" />
<Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="Opacity" Value="0.6" />
</Style>
<Style Selector="Button.copypath Path">
<Style Selector="Button.iconaction Path">
<Setter Property="Stroke" Value="{DynamicResource FidoTextMuted}" />
<Setter Property="StrokeThickness" Value="1.8" />
<Setter Property="StrokeLineCap" Value="Round" />
<Setter Property="StrokeJoin" Value="Round" />
</Style>
<Style Selector="Button.copypath /template/ ContentPresenter#PART_ContentPresenter">
<Style Selector="Button.iconaction /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="Transparent" />
</Style>
<Style Selector="Button.copypath:pointerover">
<Style Selector="Button.iconaction:pointerover">
<Setter Property="Opacity" Value="1" />
</Style>
<Style Selector="Button.copypath:pointerover /template/ ContentPresenter#PART_ContentPresenter">
<Style Selector="Button.iconaction:pointerover /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="{DynamicResource FidoGearHoverBg}" />
</Style>
<Style Selector="Button.copypath:pointerover Path">
<Style Selector="Button.iconaction:pointerover Path">
<Setter Property="Stroke" Value="{DynamicResource FidoAccentText}" />
</Style>
<!-- Nothing to act on yet (an empty flight log): dimmed, and no hover lift -->
<Style Selector="Button.iconaction:disabled">
<Setter Property="Opacity" Value="0.25" />
</Style>
<Style Selector="Button.iconaction:disabled /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="Transparent" />
</Style>

</Styles>
13 changes: 13 additions & 0 deletions src/ViewModels/MainWindowViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ namespace Fido.ViewModels;
/// </summary>
public sealed class MainWindowViewModel : ObservableObject
{
public MainWindowViewModel() =>
// The log's copy/save actions are gated on there being something to hand over.
Log.CollectionChanged += (_, _) => OnPropertyChanged(nameof(HasLog));

// --- Inputs -----------------------------------------------------------------------

private string _branchName = "";
Expand Down Expand Up @@ -531,6 +535,15 @@ static void Replace(ObservableCollection<string> target, IEnumerable<string> sou
/// <summary>Color-coded flight-log lines.</summary>
public ObservableCollection<LogLine> Log { get; } = new();

/// <summary>True once the log has a line in it — enables the copy/save actions above the panel.</summary>
public bool HasLog => Log.Count > 0;

/// <summary>
/// The whole flight log as plain text, one line per entry — what the copy-to-clipboard and
/// save-to-file actions hand over. Colour levels are presentation only and don't survive the trip.
/// </summary>
public string LogText => string.Join(Environment.NewLine, Log.Select(line => line.Text));

public void AppendLog(string message)
{
if (Dispatcher.UIThread.CheckAccess())
Expand Down
42 changes: 31 additions & 11 deletions src/Views/MainWindow.axaml
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,14 @@
Background="{DynamicResource FidoWindowBg}">

<!-- One window, one screen: header → inputs → discovery → open actions → delete → flight log.
The upper stack scrolls if the discovery list outgrows the window; the flight log keeps its
own min/max height near the bottom; the auto-close countdown pins beneath it. -->
<Grid Margin="30,26,30,24" RowDefinitions="*,Auto,Auto">
The upper stack scrolls if the discovery list outgrows the window; the flight log sits near the
bottom and takes whatever vertical slack the window has beyond the upper stack (see
MainWindow.UpdateFlightLogHeight); the auto-close countdown pins beneath it. -->
<Grid x:Name="RootGrid" Margin="30,26,30,24" RowDefinitions="*,Auto,Auto">

<ScrollViewer Grid.Row="0" VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled">
<StackPanel Spacing="22">
<ScrollViewer x:Name="MainScroller" Grid.Row="0"
VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled">
<StackPanel x:Name="UpperStack" Spacing="22">

<!-- ============================= 1. Header ============================= -->
<Grid ColumnDefinitions="Auto,*,Auto">
Expand Down Expand Up @@ -292,7 +294,7 @@
<StackPanel Grid.Column="2" Orientation="Horizontal" Spacing="10"
VerticalAlignment="Center">
<!-- Copy the selected working-tree path to the clipboard -->
<Button x:Name="CopyPathButton" Classes="copypath" Click="OnCopyPathClick"
<Button x:Name="CopyPathButton" Classes="iconaction" Click="OnCopyPathClick"
IsTabStop="False" ToolTip.Tip="Copy path to clipboard">
<Path Width="14" Height="14" Stretch="Uniform"
Data="M11,9 L20,9 A2,2 0 0 1 22,11 L22,20 A2,2 0 0 1 20,22 L11,22 A2,2 0 0 1 9,20 L9,11 A2,2 0 0 1 11,9 Z M5,15 L4,15 A2,2 0 0 1 2,13 L2,4 A2,2 0 0 1 4,2 L13,2 A2,2 0 0 1 15,4 L15,5" />
Expand Down Expand Up @@ -439,13 +441,31 @@
</ScrollViewer>

<!-- ============================= 6. Flight log ============================= -->
<Grid Grid.Row="1" RowDefinitions="Auto,Auto" Margin="0,20,0,0">
<Grid Grid.Row="0" ColumnDefinitions="Auto,*" Margin="0,0,0,9">
<Grid x:Name="LogRegion" Grid.Row="1" RowDefinitions="Auto,Auto" Margin="0,20,0,0">
<Grid Grid.Row="0" ColumnDefinitions="Auto,*,Auto" Margin="0,0,0,9">
<TextBlock Grid.Column="0" Classes="label" Text="Flight log" VerticalAlignment="Center" />
<Border Grid.Column="1" Height="1" Background="{DynamicResource FidoLogRule}"
VerticalAlignment="Center" Margin="10,0,0,0" />
VerticalAlignment="Center" Margin="10,0" />
<!-- Take the narration with you: the whole log to the clipboard, or out to a text file -->
<StackPanel Grid.Column="2" Orientation="Horizontal" Spacing="4" VerticalAlignment="Center">
<Button x:Name="CopyLogButton" Classes="iconaction" Click="OnCopyLogClick"
IsTabStop="False" IsEnabled="{Binding HasLog}"
ToolTip.Tip="Copy the flight log to the clipboard">
<Path Width="14" Height="14" Stretch="Uniform"
Data="M11,9 L20,9 A2,2 0 0 1 22,11 L22,20 A2,2 0 0 1 20,22 L11,22 A2,2 0 0 1 9,20 L9,11 A2,2 0 0 1 11,9 Z M5,15 L4,15 A2,2 0 0 1 2,13 L2,4 A2,2 0 0 1 4,2 L13,2 A2,2 0 0 1 15,4 L15,5" />
</Button>
<Button x:Name="SaveLogButton" Classes="iconaction" Click="OnSaveLogClick"
IsTabStop="False" IsEnabled="{Binding HasLog}"
ToolTip.Tip="Save the flight log to a file">
<Path Width="14" Height="14" Stretch="Uniform"
Data="M12,3 V15 M8,11 L12,15 L16,11 M4,17 V20 A1,1 0 0 0 5,21 H19 A1,1 0 0 0 20,20 V17" />
</Button>
</StackPanel>
</Grid>
<Border Grid.Row="1" Background="{DynamicResource FidoLogBg}" BorderBrush="{DynamicResource FidoLogBorder}"
<!-- MinHeight/MaxHeight are the design's content-sized panel (104 → 150 as lines land); an
explicit Height set from code-behind takes over once the window has slack to give. -->
<Border x:Name="LogPanel" Grid.Row="1"
Background="{DynamicResource FidoLogBg}" BorderBrush="{DynamicResource FidoLogBorder}"
BorderThickness="1" CornerRadius="10" Padding="15,12" MinHeight="104" MaxHeight="150">
<ScrollViewer x:Name="LogScroller" VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Auto">
<ItemsControl ItemsSource="{Binding Log}">
Expand All @@ -464,7 +484,7 @@
</Grid>

<!-- Close countdown + "keep open" escape hatch; only while an auto-close is pending -->
<Border Grid.Row="2" IsVisible="{Binding IsClosingCountdown}" Margin="0,16,0,0"
<Border x:Name="CountdownBar" Grid.Row="2" IsVisible="{Binding IsClosingCountdown}" Margin="0,16,0,0"
Background="{DynamicResource FidoGoBg}" BorderBrush="{DynamicResource FidoGo}"
BorderThickness="2,0,0,0" CornerRadius="0,5,5,0" Padding="14,10">
<Grid ColumnDefinitions="Auto,*,Auto">
Expand Down
Loading
Loading