diff --git a/.claude/rules/desktop-wpf.md b/.claude/rules/desktop-wpf.md index 6e9799a..ff91f0a 100644 --- a/.claude/rules/desktop-wpf.md +++ b/.claude/rules/desktop-wpf.md @@ -38,3 +38,13 @@ Grep 으로 심볼 위치를 찾고 해당 구간만 Read 한다. 분할 리팩 `VideoDownloader.App` 은 `InternalsVisibleTo("VideoDownloader.Tests")` 가 걸려 있다. 테스트를 위해 멤버를 `public` 으로 승격하지 말고 `internal` 로 두면 된다. + +## 아이콘 글리프 (PUA · Segoe Fluent Icons) + +- **암시적(키 없는) TextBlock 스타일에 FontFamily 를 넣지 않는다.** 버튼 ContentPresenter 가 만드는 + 자동 TextBlock 이 IconFont 상속을 잃어 모든 아이콘이 tofu(□)가 된다(2026-08-18 실결함 — 원인 규명에 + 실캡처·픽셀 비교까지 필요했다). UI 폰트 통일은 각 창 루트의 `FontFamily="{DynamicResource UiFont}"` + 상속으로 한다. 코드 생성 창은 `SetResourceReference(FontFamilyProperty, "UiFont")`. +- PUA 글리프는 IconFont 보장 스타일(IconButton·FluentIcon·CaptionButton 계열)이나 명시 + `FontFamily="{DynamicResource IconFont}"` 에서만 쓴다. **한 문자열에 PUA 와 일반 텍스트를 섞지 않는다.** + `Infrastructure/IconGlyphXamlTests` 가 정적으로, `E2E/IconContentFontTests` 가 실창에서 이를 강제한다. diff --git a/.claude/skills/release/SKILL.md b/.claude/skills/release/SKILL.md index 13b832f..7df4259 100644 --- a/.claude/skills/release/SKILL.md +++ b/.claude/skills/release/SKILL.md @@ -25,7 +25,7 @@ git status --short ``` - 하는 일: `VideoDownloader.App` 을 `win-x64`/`win-arm64` self-contained 로 publish (`publish//`) - → Inno Setup 으로 설치자 컴파일 → `out/VideoDownloader-Setup-.exe` + → Inno Setup 으로 설치자 컴파일 → `out/MediaBrowser-Setup-.exe` - 필요: **Inno Setup 6** (`winget install --id JRSoftware.InnoSetup -e`). 없으면 스크립트가 명시적으로 throw 한다. - 특정 아키텍처만: `-Archs x64` diff --git a/.forgejo/workflows/release.yml b/.forgejo/workflows/release.yml index b58009b..09502a7 100644 --- a/.forgejo/workflows/release.yml +++ b/.forgejo/workflows/release.yml @@ -74,7 +74,7 @@ jobs: run: ./build/publish.ps1 -Config Release -Version $env:APP_VERSION - name: 릴리스 업로드 env: { RELEASE_TOKEN: "${{ secrets.RELEASE_TOKEN }}" } - run: ./build/ci/upload-release-asset.ps1 -Tag $env:RELTAG -Files out/VideoDownloader-Setup-x64.exe, out/VideoDownloader-Setup-arm64.exe + run: ./build/ci/upload-release-asset.ps1 -Tag $env:RELTAG -Files out/MediaBrowser-Setup-x64.exe, out/MediaBrowser-Setup-arm64.exe # ── Android APK (키스토어 시크릿 없으면 디버그 서명 APK) ── android: @@ -126,10 +126,10 @@ jobs: $apk = Get-ChildItem out/android/*-Signed.apk | Select-Object -First 1 if (-not $apk) { $apk = Get-ChildItem out/android/*.apk | Select-Object -First 1 } if (-not $apk) { throw "APK 산출물 없음" } - Copy-Item $apk.FullName "out/VideoDownloader-$env:APP_VERSION-arm64.apk" + Copy-Item $apk.FullName "out/MediaBrowser-$env:APP_VERSION-arm64.apk" - name: 릴리스 업로드 env: { RELEASE_TOKEN: "${{ secrets.RELEASE_TOKEN }}" } - run: ./build/ci/upload-release-asset.ps1 -Tag $env:RELTAG -Files "out/VideoDownloader-$env:APP_VERSION-arm64.apk" + run: ./build/ci/upload-release-asset.ps1 -Tag $env:RELTAG -Files "out/MediaBrowser-$env:APP_VERSION-arm64.apk" # ── Linux 셸 (tar.gz self-contained — Cli + Avalonia, x64 네이티브 + arm64 교차) ── linux: @@ -163,11 +163,11 @@ jobs: for rid in linux-x64 linux-arm64; do dotnet publish VideoDownloader.Cli -c Release -r "$rid" --self-contained -o "out/cli-$rid" dotnet publish VideoDownloader.Avalonia -c Release -r "$rid" --self-contained -o "out/avalonia-$rid" - tar -C out -czf "out/videodownloader-$rid.tar.gz" "cli-$rid" "avalonia-$rid" + tar -C out -czf "out/mediabrowser-$rid.tar.gz" "cli-$rid" "avalonia-$rid" done - name: 릴리스 업로드 env: { RELEASE_TOKEN: "${{ secrets.RELEASE_TOKEN }}" } - run: ./build/ci/upload-release-asset.sh "$RELTAG" out/videodownloader-linux-x64.tar.gz out/videodownloader-linux-arm64.tar.gz + run: ./build/ci/upload-release-asset.sh "$RELTAG" out/mediabrowser-linux-x64.tar.gz out/mediabrowser-linux-arm64.tar.gz # ── macOS 셸 (tar.gz — arm64 네이티브 + x64 교차. 서명·공증은 D4, Apple Developer Program 후) ── macos: @@ -201,8 +201,8 @@ jobs: for rid in osx-arm64 osx-x64; do dotnet publish VideoDownloader.Cli -c Release -r "$rid" --self-contained -o "out/cli-$rid" dotnet publish VideoDownloader.Avalonia -c Release -r "$rid" --self-contained -o "out/avalonia-$rid" - tar -C out -czf "out/videodownloader-$rid.tar.gz" "cli-$rid" "avalonia-$rid" + tar -C out -czf "out/mediabrowser-$rid.tar.gz" "cli-$rid" "avalonia-$rid" done - name: 릴리스 업로드 env: { RELEASE_TOKEN: "${{ secrets.RELEASE_TOKEN }}" } - run: ./build/ci/upload-release-asset.sh "$RELTAG" out/videodownloader-osx-arm64.tar.gz out/videodownloader-osx-x64.tar.gz + run: ./build/ci/upload-release-asset.sh "$RELTAG" out/mediabrowser-osx-arm64.tar.gz out/mediabrowser-osx-x64.tar.gz diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 969703e..3a67cd0 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -52,7 +52,7 @@ jobs: - uses: actions/upload-artifact@v4 with: name: win-${{ matrix.arch }} - path: out/VideoDownloader-Setup-${{ matrix.arch }}.exe + path: out/MediaBrowser-Setup-${{ matrix.arch }}.exe # ── Linux 셸 (tar.gz self-contained — Avalonia + Cli) ── linux: @@ -74,11 +74,11 @@ jobs: run: | dotnet publish VideoDownloader.Cli -c Release -r ${{ matrix.rid }} --self-contained -o out/cli-${{ matrix.rid }} dotnet publish VideoDownloader.Avalonia -c Release -r ${{ matrix.rid }} --self-contained -o out/avalonia-${{ matrix.rid }} - tar -C out -czf out/videodownloader-${{ matrix.rid }}.tar.gz cli-${{ matrix.rid }} avalonia-${{ matrix.rid }} + tar -C out -czf out/mediabrowser-${{ matrix.rid }}.tar.gz cli-${{ matrix.rid }} avalonia-${{ matrix.rid }} - uses: actions/upload-artifact@v4 with: name: ${{ matrix.rid }} - path: out/videodownloader-${{ matrix.rid }}.tar.gz + path: out/mediabrowser-${{ matrix.rid }}.tar.gz # ── macOS 셸 (tar.gz — D4 서명·공증은 Developer ID 시크릿 등록 후 확장) ── macos: @@ -100,12 +100,12 @@ jobs: run: | dotnet publish VideoDownloader.Cli -c Release -r ${{ matrix.rid }} --self-contained -o out/cli-${{ matrix.rid }} dotnet publish VideoDownloader.Avalonia -c Release -r ${{ matrix.rid }} --self-contained -o out/avalonia-${{ matrix.rid }} - tar -C out -czf out/videodownloader-${{ matrix.rid }}.tar.gz cli-${{ matrix.rid }} avalonia-${{ matrix.rid }} + tar -C out -czf out/mediabrowser-${{ matrix.rid }}.tar.gz cli-${{ matrix.rid }} avalonia-${{ matrix.rid }} # D4: MACOS_P12/KEYCHAIN_PASS/NOTARY_PROFILE 시크릿 등록 시 codesign+notarytool+DMG 스텝 추가 - uses: actions/upload-artifact@v4 with: name: ${{ matrix.rid }} - path: out/videodownloader-${{ matrix.rid }}.tar.gz + path: out/mediabrowser-${{ matrix.rid }}.tar.gz # ── Android APK (D2 — 서명 키스토어 시크릿 없으면 미서명 APK) ── android: diff --git a/README.md b/README.md index 7732f2b..6b906f4 100644 --- a/README.md +++ b/README.md @@ -56,7 +56,7 @@ dotnet build VideoDownloader.Mobile -f net10.0-android ## 배포 ```powershell -# Windows 설치자 (x64 + arm64) → out/VideoDownloader-Setup-{arch}.exe +# Windows 설치자 (x64 + arm64) → out/MediaBrowser-Setup-{arch}.exe ./build/publish.ps1 -Config Release -Version 1.0.0 # macOS / Linux 원격 빌드 (.env 의 빌드머신 자격 증명 사용) → out/remote// diff --git a/VideoDownloader.App/Browser/PairingWindow.cs b/VideoDownloader.App/Browser/PairingWindow.cs index f8fe93a..8e044c2 100644 --- a/VideoDownloader.App/Browser/PairingWindow.cs +++ b/VideoDownloader.App/Browser/PairingWindow.cs @@ -15,9 +15,11 @@ public sealed class PairingWindow : Window { public PairingWindow(ServerHost server, string deviceName) { - Title = "기기 연결 — Video Downloader"; + Title = "기기 연결 - MediaBrowser"; Width = 420; Height = 560; WindowStartupLocation = WindowStartupLocation.CenterOwner; + SetResourceReference(FontFamilyProperty, "UiFont"); // 암시 스타일 폰트 강제 제거 후 창 루트 상속으로 통일 + Background = System.Windows.Media.Brushes.White; var lanIp = GetLanIp(); diff --git a/VideoDownloader.App/Browser/PlayerWindow.cs b/VideoDownloader.App/Browser/PlayerWindow.cs index b116cd1..17f7c44 100644 --- a/VideoDownloader.App/Browser/PlayerWindow.cs +++ b/VideoDownloader.App/Browser/PlayerWindow.cs @@ -28,9 +28,11 @@ public sealed class PlayerWindow : Window _config = config; _playlist = playlist; - Title = "재생목록 — Video Downloader"; + Title = "재생목록 - MediaBrowser"; Width = 900; Height = 620; WindowStartupLocation = WindowStartupLocation.CenterOwner; + SetResourceReference(FontFamilyProperty, "UiFont"); // 암시 스타일 폰트 강제 제거 후 창 루트 상속으로 통일 + var bar = new Border { Background = System.Windows.Media.Brushes.Black, Padding = new Thickness(8, 6, 8, 6) }; var dock = new DockPanel(); diff --git a/VideoDownloader.App/MainWindow.Usecases.cs b/VideoDownloader.App/MainWindow.Usecases.cs index ef226e7..db3b801 100644 --- a/VideoDownloader.App/MainWindow.Usecases.cs +++ b/VideoDownloader.App/MainWindow.Usecases.cs @@ -164,7 +164,7 @@ public partial class MainWindow try { var current = System.Reflection.Assembly.GetExecutingAssembly().GetName().Version?.ToString(3) ?? "0.0.0"; - var result = await new VideoDownloader.Core.Update.UpdateChecker().CheckAsync(repo, current, "win-x64"); + var result = await new VideoDownloader.Core.Update.UpdateChecker().CheckAsync(repo, current, "win-x64", _config.UpdateApiBase); if (result.IsNewer) { UpdateBadge.Visibility = Visibility.Visible; @@ -656,7 +656,7 @@ public partial class MainWindow private void LibraryViewToggle_Click(object sender, RoutedEventArgs e) { _libraryGrid = !_libraryGrid; - LibraryViewToggle.Content = _libraryGrid ? "\uE8FD; 그리드" : "\uE71B; 리스트"; + LibraryViewToggle.Content = _libraryGrid ? "그리드" : "리스트"; RefreshLibrary(); } diff --git a/VideoDownloader.App/MainWindow.xaml b/VideoDownloader.App/MainWindow.xaml index 1fb88ca..657ec3f 100644 --- a/VideoDownloader.App/MainWindow.xaml +++ b/VideoDownloader.App/MainWindow.xaml @@ -9,13 +9,13 @@ xmlns:feat="clr-namespace:VideoDownloader.App.Browser.Features" mc:Ignorable="d" FontFamily="{DynamicResource UiFont}" - Title="Video Downloader" Height="820" Width="1300" + Title="MediaBrowser" Height="820" Width="1300" Background="{DynamicResource WindowBgGradient}" WindowStartupLocation="CenterScreen" UseLayoutRounding="True" ToolTipService.InitialShowDelay="400" ToolTipService.ShowDuration="8000" - AutomationProperties.Name="Video Downloader 메인 창" + AutomationProperties.Name="MediaBrowser 메인 창" WindowStyle="None" ResizeMode="CanResize"> @@ -273,7 +273,7 @@ - @@ -389,7 +389,7 @@ Background="Transparent" BorderThickness="0" Padding="0" AutomationProperties.Name="클립보드 링크 받기 제안"> - + @@ -500,7 +500,7 @@ Foreground="{DynamicResource TextMuted}" Visibility="Collapsed" AutomationProperties.Name="잠긴 탭"/>