diff --git a/AGENTS.md b/AGENTS.md
index d6cc679..64cb29d 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -1,5 +1,10 @@
# AGENTS.md
+## AI 行为准则
+
+- **称呼规范**:始终称呼用户为「思谦」或「王思谦」。
+- **信息缺失处理**:遇到任何不确定、拿不准或信息缺失的情况,不要自行推测。先收集整理好问题,停下来向思谦确认后再继续。
+
## Build & Test
```bash
@@ -26,23 +31,14 @@ dotnet run --project src/GUI # Launch the Avalonia de
## Known Issues / Gotchas
-
- 扫描时如果报错,报错信息可能会填满整个窗口,导致Overlay无法关闭。
- 读取时如果报错,没有任何警告,会静默报错。需要有一个界面右下方的toast,或是发送系统通知告知用户遇到了错误。
-- 扫描界面的宽度会因为文件名而发生变化。固定为窗口宽度的50%。如果文件名超过这个宽度则省略中间的字符,仅保留开头以及最后10个字符。
- **升级 Avalonia 版本时,必须同步更新所有次级包引用。** 仅修改主包 `Avalonia` 的 Version 不会自动更新 `Avalonia.Desktop`、`Avalonia.Themes.Fluent`、`Avalonia.Fonts.Inter` 等次级包。必须手动逐个修改,然后执行 `dotnet clean && dotnet restore`。否则运行时的原生库(如 `libAvaloniaNative.dylib`)会混用新旧版本,导致 macOS 上的 `StorageProvider` 文件/文件夹选择对话框在回调时 SIGSEGV 崩溃。
- 相关 Avalonia issues: [#21102](https://github.com/AvaloniaUI/Avalonia/issues/21102), [#21150](https://github.com/AvaloniaUI/Avalonia/issues/21150), [#21313](https://github.com/AvaloniaUI/Avalonia/issues/21313),修复 PR: [#21104](https://github.com/AvaloniaUI/Avalonia/pull/21104)。
- **Tests create temp files/directories** under `Path.GetTempPath()` and clean them up via `IDisposable`. Don't rely on a real audio directory for tests.
- **`AudioMetadataReader` tests** use real WAV files from `data/source/` (not in git). When files are absent, tests silently return early (`if (wavFiles.Length == 0) return;`) — they pass without actually running assertions, not `Skip.If`. Running `dotnet test` will report 12 passes regardless of whether the fixture files exist.
- **`.atl.txt` fixture files** are generated by `AtlFieldExtractor`. If the WAV files or the ATL library version change, re-run the extractor to regenerate them before running metadata tests.
-
-## Data / Test Fixtures
-
-- **`data/source/`** 包含有版权保护的 WAV 音频文件,**不会提交到 git**。按厂商分子目录:`Boom/`、`Wow Sound/`、`The Odessy/`、`Sound Idea/`。
- - 每个 WAV 旁有对应的 `.atl.txt` 文件(由 `AtlFieldExtractor` 生成),记录了 ATL 库解析出的完整元数据,供测试断言参考。
- - Boom 目录下的文件元数据最丰富(标准标签 + iXML 自定义字段 + BWF bext 字段 + 嵌入封面);Sound Idea 目录下的文件元数据最少(仅基础技术参数,无任何附加字段)。
-- **`AudioMetadataReader` 测试**使用 `data/source/Boom/` 和 `Sound Idea/` 中的 WAV 文件。文件不在 git 仓库中,测试通过 `if (wavFiles.Length == 0) return;` 静默返回(测试仍然 pass,但未执行实际断言)。
-- **路径解析**:测试运行时从 `bin/Debug/net10.0/` 启动,需向上回溯 6 级到达仓库根目录,再拼接 `data/source/`。
+- 需要一个UI设计。现在的界面属实有点丑陋了。
## Conventions
diff --git a/src/GUI/Converters/ValueConverters.cs b/src/GUI/Converters/ValueConverters.cs
index b255c5e..1e3b729 100644
--- a/src/GUI/Converters/ValueConverters.cs
+++ b/src/GUI/Converters/ValueConverters.cs
@@ -100,6 +100,43 @@ public class InverseBoolConverter : IValueConverter
}
}
+public class HalfDoubleConverter : IValueConverter
+{
+ public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
+ {
+ if (value is double d && !double.IsNaN(d) && !double.IsInfinity(d))
+ return d / 2.0;
+ return 400.0;
+ }
+
+ public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
+ => throw new NotSupportedException();
+}
+
+public class MiddleTruncateConverter : IValueConverter
+{
+ private const int KeepTail = 10;
+
+ public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
+ {
+ if (value is not string text || string.IsNullOrEmpty(text)) return value;
+
+ int maxLength = 60;
+ if (parameter != null && int.TryParse(parameter.ToString(), NumberStyles.Integer, CultureInfo.InvariantCulture, out int parsed))
+ maxLength = parsed;
+
+ if (text.Length <= maxLength) return text;
+
+ int head = maxLength - KeepTail - 3;
+ if (head <= 0) return text[..maxLength];
+
+ return string.Concat(text.AsSpan(0, head), "...", text.AsSpan(text.Length - KeepTail, KeepTail));
+ }
+
+ public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
+ => throw new NotSupportedException();
+}
+
public static class ValueConverters
{
public static readonly IValueConverter DurationFormat = new DurationFormatConverter();
@@ -109,4 +146,6 @@ public static class ValueConverters
public static readonly IValueConverter DurationToSliderMax = new DurationToSliderMaxConverter();
public static readonly IValueConverter PositionFormat = new PositionFormatConverter();
public static readonly IValueConverter InverseBool = new InverseBoolConverter();
+ public static readonly IValueConverter HalfDouble = new HalfDoubleConverter();
+ public static readonly IValueConverter MiddleTruncate = new MiddleTruncateConverter();
}
diff --git a/src/GUI/Views/MainWindow.axaml b/src/GUI/Views/MainWindow.axaml
index a1862be..4b4d7fe 100644
--- a/src/GUI/Views/MainWindow.axaml
+++ b/src/GUI/Views/MainWindow.axaml
@@ -16,15 +16,15 @@
-
+
-
-
+
+
diff --git a/src/GUI/Views/ScanProgressView.axaml b/src/GUI/Views/ScanProgressView.axaml
index 669034f..7ae413b 100644
--- a/src/GUI/Views/ScanProgressView.axaml
+++ b/src/GUI/Views/ScanProgressView.axaml
@@ -1,6 +1,7 @@
+
+
+
+
+
+ Width="{Binding $parent[Window].Width, Converter={StaticResource HalfDouble}}">
@@ -27,8 +33,8 @@
-
+
diff --git a/src/Resonance.sln.DotSettings.user b/src/Resonance.sln.DotSettings.user
index 762a05c..c92da14 100755
--- a/src/Resonance.sln.DotSettings.user
+++ b/src/Resonance.sln.DotSettings.user
@@ -2,6 +2,8 @@
ForceIncluded
ForceIncluded
ForceIncluded
+ ForceIncluded
+ ForceIncluded
ForceIncluded
ForceIncluded
ForceIncluded