From 88bab0ed847abc00d8d6f270aa8e216baf2bba26 Mon Sep 17 00:00:00 2001 From: Firnschnee <99618191+Firnschnee@users.noreply.github.com> Date: Tue, 2 Jun 2026 09:07:21 +0200 Subject: [PATCH 1/2] fix(windows): bake app name into assembly so taskbar right-click shows it The taskbar right-click menu showed "app-it-host" instead of the app name (#9). Windows reads that label from the executable's embedded FileDescription, which the build-once-then-rename flow left at the project default. desktop-build.ps1 now publishes the WebView2 host per-app with -p:AssemblyName="". The .csproj sets neither AssemblyTitle nor Product, so that one flag cascades into FileDescription + ProductName + OriginalFilename, which all then read the app name. Per-app exes are cached under assets\build\wrapper-windows\\ (mtime-aware); the trade vs the old single shared publish is one dotnet publish per app on first build. The per-app publish runs inside the build loop; a publish failure falls that app back to the Edge launcher (and skips the host build for later apps). dotnet stdout is piped to Out-Host so it cannot pollute the function return value. Validated on real Windows 11 hardware. Embedded metadata reads the app name; a freshly-named host shows it in the taskbar right-click immediately (an already-seen exe shows it after the shell cache turns over). PSScriptAnalyzer clean against the repo settings. --- CHANGELOG.md | 1 + .../templates/desktop-build.ps1 | 110 +++++++++++------- 2 files changed, 71 insertions(+), 40 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fb5ca84..e7d96cc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ - Added: `desktop:doctor` — a self-diagnosing command for generated `app-it` launchers (`scripts/desktop-doctor.sh`). Run `npm run desktop:doctor` long after the build session to get a short, issue-ready report on one launcher: config + placeholder leakage, installed/build `.app`, Info.plist identity, ad-hoc signature, quarantine / iCloud signature-breaking xattrs, preferred-vs-runtime port, stale PID, **whether the process on the runtime port is actually in the recorded supervisor's descendant tree** (reuses the launcher's reattach gate), start-command binary resolution on the launcher's PATH, log/state paths, and **template drift** (feature-probes the installed `wrapper`/`run` against the current templates — no version stamp needed). `--tail[=N]` appends the launcher log. It is a diagnostic, not a fixer: read-only, deterministic, local (no network, no new dependencies), and it says "probably" when a check can't be certain. The opt-in `--fix-safe` flag touches **only app-it's own generated state** — stale pid/port files, this bundle's stale LaunchServices registration, the rebuilt icon, and quarantine on the generated `.app` — never the user's product code, dependencies, config, or anything outside app-it's artifacts. macOS `app-it` plugin only (the `app-it-static` companion has a different runtime model). Embodies Core principle #8 (*runtime truth beats build-time guess*) for end users. - Added: `app-it-static` companion plugin (`plugins/app-it-static/`) — a macOS sibling of `app-it` for **finished or buildable** apps. Builds once, then serves the built output (`dist/`/`build/`/`out/`/…) from a tiny zero-dependency static server (~15 MB) or directly via `file://` (~0 MB) — **no dev server**, instead of the 300–700 MB a dev server holds. Reuses `app-it`'s native Swift WebKit window, icon pipeline, and one-folder Dock install (the five shared templates are byte-identical and CI guards them against drift). The served output is a snapshot; `desktop:rebuild` refreshes it. Inspired by r/ClaudeAI launch feedback (see README → Community nudge) and recorded in [ADR 0006](docs/decisions/0006-static-companion-snapshot-model.md). - Added: Windows beta scaffold (`plugins/app-it-windows/`) — a sibling plugin mirroring the macOS contract with Windows primitives (WPF + WebView2 host, PowerShell lifecycle scripts, multi-resolution `.ico`, Start Menu `.lnk`). Build + lint gated by a required `windows-latest` CI job; **untested on real hardware, looking for a maintainer.** See [docs/WINDOWS.md](docs/WINDOWS.md). +- Fixed (Windows beta): the generated launcher's **taskbar right-click menu showed `app-it-host`** instead of the app name (#9). The shell reads that label from the executable's embedded `FileDescription`, which the former build-once-then-rename flow left at the project default. `desktop-build.ps1` now publishes the WebView2 host **per app** with `-p:AssemblyName=""` — the `.csproj` sets neither `AssemblyTitle` nor `Product`, so that one flag cascades into `FileDescription` + `ProductName` + `OriginalFilename`, all of which then read the app name. Per-app published exes are cached under `assets\build\wrapper-windows\\` (mtime-aware), so the trade vs the old single shared publish is one `dotnet publish` per app on first build. Validated on real Windows 11 hardware. (Note: the Windows shell caches taskbar identity per executable, so an existing app shows the new name only after its cache turns over; a clean install shows it immediately.) ## 0.1.0 - 2026-05-30 diff --git a/plugins/app-it-windows/skills/app-it-windows/templates/desktop-build.ps1 b/plugins/app-it-windows/skills/app-it-windows/templates/desktop-build.ps1 index 94483d2..b556026 100644 --- a/plugins/app-it-windows/skills/app-it-windows/templates/desktop-build.ps1 +++ b/plugins/app-it-windows/skills/app-it-windows/templates/desktop-build.ps1 @@ -5,8 +5,9 @@ # # Windows reading of the macOS build: instead of a .app bundle per app, produce # desktop\\ -# .exe — the published WPF + WebView2 host (renamed so the -# taskbar/Process name is the app, not "wrapper-windows") +# .exe — the WPF + WebView2 host, published per-app with the app +# name baked into the assembly so the taskbar entry, +# process name and right-click read the app (issue #9) # .ico — the multi-resolution icon (from the icon step, 2.4) # run.ps1 — substituted run-template.ps1: the thin bootstrap the # Start Menu .lnk launches (desktop-install.ps1) @@ -77,13 +78,21 @@ if ($globalMode -eq 'webview2' -and -not $hasDotnetSdk) { $runTemplateWebView2 = Join-Path $ScriptDir 'run-template.ps1' $runTemplateEdge = Join-Path $ScriptDir 'run-template-edge.ps1' -# --- Resolve & publish the WPF host once (cached) ---------------------------- +# --- Resolve the WPF host project (published per-app below) ------------------ # Locate the wrapper-windows .csproj near these templates (step 2.2). Override -# with APP_IT_WRAPPER_CSPROJ. Published self-contained single-file exe is cached -# under assets\build\ and reused unless a source file is newer. -$publishedExe = $null +# with APP_IT_WRAPPER_CSPROJ. Unlike the former single shared publish, each app +# is published separately so its name can be baked into the assembly metadata +# (issue #9): -p:AssemblyName="" sets the embedded FileDescription / +# ProductName (they inherit from AssemblyName, which the .csproj leaves at its +# default), and those are what the Windows taskbar right-click reads. A shared +# binary can't carry per-app identity because the version resource is baked at +# compile time; rewriting it post-publish would need an external resource editor +# (rcedit), which the no-dependencies contract rules out. Per-app exes are cached +# under assets\build\wrapper-windows\\ and reused unless a source is newer. +$csproj = $null +$projDir = $null +$newestSrc = $null if ($globalMode -eq 'webview2') { - $csproj = $null if ($env:APP_IT_WRAPPER_CSPROJ -and (Test-Path $env:APP_IT_WRAPPER_CSPROJ)) { $csproj = $env:APP_IT_WRAPPER_CSPROJ } else { @@ -96,42 +105,55 @@ if ($globalMode -eq 'webview2') { Write-Warning 'Set APP_IT_WRAPPER_CSPROJ to the host project if it lives elsewhere.' $globalMode = 'edge' } else { - $publishDir = Join-Path $Root 'assets\build\wrapper-windows' $projDir = Split-Path -Parent $csproj $newestSrc = Get-ChildItem -Path $projDir -Recurse -Include *.cs,*.csproj,*.xaml -ErrorAction SilentlyContinue | Sort-Object LastWriteTimeUtc -Descending | Select-Object -First 1 - $existing = if (Test-Path $publishDir) { - Get-ChildItem -Path $publishDir -Filter *.exe -ErrorAction SilentlyContinue | Select-Object -First 1 - } else { $null } - $needsBuild = (-not $existing) -or ($newestSrc -and $newestSrc.LastWriteTimeUtc -gt $existing.LastWriteTimeUtc) - if ($needsBuild) { - Write-Host "Publishing WebView2 host: $csproj" - # Self-contained single-file win-x64 (ADR 0005): one .exe, no runtime install. - & dotnet publish $csproj -c Release -r win-x64 --self-contained true ` - -p:PublishSingleFile=true -p:IncludeNativeLibrariesForSelfExtract=true ` - -o $publishDir - if ($LASTEXITCODE -ne 0) { - Write-Warning 'dotnet publish failed - falling back to the Edge --app launcher.' - $globalMode = 'edge' - } - } - if ($globalMode -eq 'webview2') { - # AssemblyName is app-it-host -> app-it-host.exe; fall back to the - # largest .exe if a maintainer renamed the assembly. - $publishedExe = Get-ChildItem -Path $publishDir -Filter 'app-it-host.exe' -ErrorAction SilentlyContinue | - Select-Object -First 1 -ExpandProperty FullName - if (-not $publishedExe) { - $publishedExe = Get-ChildItem -Path $publishDir -Filter *.exe -ErrorAction SilentlyContinue | - Sort-Object Length -Descending | Select-Object -First 1 -ExpandProperty FullName - } - if (-not $publishedExe) { - Write-Warning 'No .exe produced by dotnet publish - falling back to the Edge --app launcher.' - $globalMode = 'edge' - } - } } } +# Publish the host for one app, baking the app name into the assembly so the +# taskbar right-click, FileDescription and OriginalFilename read the app name +# instead of "app-it-host" (issue #9). Cached per slug, mtime-aware. Returns the +# published ".exe" path, or $null on a build failure (the caller then +# falls that app back to the Edge launcher). +function Publish-HostForApp { + param([string]$AppName, [string]$Slug) + + $appPublishDir = Join-Path $Root "assets\build\wrapper-windows\$Slug" + $existing = if (Test-Path $appPublishDir) { + Get-ChildItem -Path $appPublishDir -Filter "$AppName.exe" -ErrorAction SilentlyContinue | Select-Object -First 1 + } else { $null } + $needsBuild = (-not $existing) -or ($newestSrc -and $newestSrc.LastWriteTimeUtc -gt $existing.LastWriteTimeUtc) + + if ($needsBuild) { + Write-Host "Publishing WebView2 host for '$AppName': $csproj" + # -p:AssemblyName="" names the output .exe and, since + # the .csproj sets neither AssemblyTitle nor Product, cascades into the + # embedded FileDescription + ProductName (verified on Windows 11). The + # "AssemblyName=$AppName" token carries no literal space, so it survives + # native arg-passing under both pwsh 7 (Windows mode) and Windows + # PowerShell 5.1 (Legacy). Self-contained single-file per ADR 0005. + # Pipe to Out-Host: dotnet's stdout must not leak into the function's + # output stream, or the returned $exe path is polluted with build log + # lines. Out-Host keeps the progress visible while returning nothing. + & dotnet publish $csproj -c Release -r win-x64 --self-contained true ` + -p:PublishSingleFile=true -p:IncludeNativeLibrariesForSelfExtract=true ` + -p:AssemblyName=$AppName ` + -o $appPublishDir | Out-Host + if ($LASTEXITCODE -ne 0) { return $null } + } + + $exe = Get-ChildItem -Path $appPublishDir -Filter "$AppName.exe" -ErrorAction SilentlyContinue | + Select-Object -First 1 -ExpandProperty FullName + if (-not $exe) { + # Name didn't map 1:1 (MSBuild stripped unusual chars) - take the + # largest .exe as the host. + $exe = Get-ChildItem -Path $appPublishDir -Filter *.exe -ErrorAction SilentlyContinue | + Sort-Object Length -Descending | Select-Object -First 1 -ExpandProperty FullName + } + return $exe +} + # --- Substitution helper ----------------------------------------------------- # Strips a TEMPLATE-DOCS block (if present) before substitution so placeholder # examples inside header comments don't leak substituted values into the built @@ -152,10 +174,18 @@ foreach ($app in $apps) { Write-Host "Building: $appDir (mode: $appMode)" New-Item -ItemType Directory -Force -Path $appDir | Out-Null - # Host .exe (webview2 mode only): rename to .exe so the taskbar - # entry and process name are the app, not "wrapper-windows". + # Host .exe (webview2 mode only): published per-app with the app name baked + # into the assembly metadata (issue #9), so the taskbar entry, process name + # AND the taskbar right-click all read the app, not "app-it-host". if ($appMode -eq 'webview2') { - Copy-Item -Force -Path $publishedExe -Destination (Join-Path $appDir "$($app.name).exe") + $exe = Publish-HostForApp -AppName $app.name -Slug $app.slug + if ($exe) { + Copy-Item -Force -Path $exe -Destination (Join-Path $appDir "$($app.name).exe") + } else { + Write-Warning " dotnet publish failed for $($app.name) - falling back to the Edge --app launcher." + $appMode = 'edge' + $globalMode = 'edge' # a broken build won't fix itself; skip publish for later apps + } } # Icon: built by the icon step (2.4). Call desktop-icons.ps1 if present From 0542685ae500112c90776c989b059d0cdb634e43 Mon Sep 17 00:00:00 2001 From: Firnschnee <99618191+Firnschnee@users.noreply.github.com> Date: Tue, 2 Jun 2026 09:23:20 +0200 Subject: [PATCH 2/2] fix(windows): address review - split assembly identity from file name Reworks the #9 fix in response to PR review (sourcery-ai): - Publish with -p:AssemblyName= (always [a-z0-9-], a valid assembly/file name) and -p:AssemblyTitle/-p:Product=. The slug drives the published file name, so the per-app cache lookup is exact - no MSBuild char-stripping to second-guess (the prior code keyed the cache on ".exe" and would rebuild every run if MSBuild altered the name). The free-form app name now only feeds FileDescription/ProductName, the fields the taskbar reads, so it never has to be a valid file name - removing the invalid-char failure mode for -p:AssemblyName. - Pass build inputs (csproj, repo root, newest-source mtime) into Publish-HostForApp as parameters instead of reading script scope. - Use Get-Item -LiteralPath for the cache probe. Verified on Windows 11: a fresh-named app shows its name in the taskbar right-click on first launch; FileDescription/ProductName read the app name, OriginalFilename is the slug; a second build hits the cache (no republish). PSScriptAnalyzer clean. --- CHANGELOG.md | 2 +- .../templates/desktop-build.ps1 | 93 ++++++++++--------- 2 files changed, 51 insertions(+), 44 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e7d96cc..04fb66f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,7 +14,7 @@ - Added: `desktop:doctor` — a self-diagnosing command for generated `app-it` launchers (`scripts/desktop-doctor.sh`). Run `npm run desktop:doctor` long after the build session to get a short, issue-ready report on one launcher: config + placeholder leakage, installed/build `.app`, Info.plist identity, ad-hoc signature, quarantine / iCloud signature-breaking xattrs, preferred-vs-runtime port, stale PID, **whether the process on the runtime port is actually in the recorded supervisor's descendant tree** (reuses the launcher's reattach gate), start-command binary resolution on the launcher's PATH, log/state paths, and **template drift** (feature-probes the installed `wrapper`/`run` against the current templates — no version stamp needed). `--tail[=N]` appends the launcher log. It is a diagnostic, not a fixer: read-only, deterministic, local (no network, no new dependencies), and it says "probably" when a check can't be certain. The opt-in `--fix-safe` flag touches **only app-it's own generated state** — stale pid/port files, this bundle's stale LaunchServices registration, the rebuilt icon, and quarantine on the generated `.app` — never the user's product code, dependencies, config, or anything outside app-it's artifacts. macOS `app-it` plugin only (the `app-it-static` companion has a different runtime model). Embodies Core principle #8 (*runtime truth beats build-time guess*) for end users. - Added: `app-it-static` companion plugin (`plugins/app-it-static/`) — a macOS sibling of `app-it` for **finished or buildable** apps. Builds once, then serves the built output (`dist/`/`build/`/`out/`/…) from a tiny zero-dependency static server (~15 MB) or directly via `file://` (~0 MB) — **no dev server**, instead of the 300–700 MB a dev server holds. Reuses `app-it`'s native Swift WebKit window, icon pipeline, and one-folder Dock install (the five shared templates are byte-identical and CI guards them against drift). The served output is a snapshot; `desktop:rebuild` refreshes it. Inspired by r/ClaudeAI launch feedback (see README → Community nudge) and recorded in [ADR 0006](docs/decisions/0006-static-companion-snapshot-model.md). - Added: Windows beta scaffold (`plugins/app-it-windows/`) — a sibling plugin mirroring the macOS contract with Windows primitives (WPF + WebView2 host, PowerShell lifecycle scripts, multi-resolution `.ico`, Start Menu `.lnk`). Build + lint gated by a required `windows-latest` CI job; **untested on real hardware, looking for a maintainer.** See [docs/WINDOWS.md](docs/WINDOWS.md). -- Fixed (Windows beta): the generated launcher's **taskbar right-click menu showed `app-it-host`** instead of the app name (#9). The shell reads that label from the executable's embedded `FileDescription`, which the former build-once-then-rename flow left at the project default. `desktop-build.ps1` now publishes the WebView2 host **per app** with `-p:AssemblyName=""` — the `.csproj` sets neither `AssemblyTitle` nor `Product`, so that one flag cascades into `FileDescription` + `ProductName` + `OriginalFilename`, all of which then read the app name. Per-app published exes are cached under `assets\build\wrapper-windows\\` (mtime-aware), so the trade vs the old single shared publish is one `dotnet publish` per app on first build. Validated on real Windows 11 hardware. (Note: the Windows shell caches taskbar identity per executable, so an existing app shows the new name only after its cache turns over; a clean install shows it immediately.) +- Fixed (Windows beta): the generated launcher's **taskbar right-click menu showed `app-it-host`** instead of the app name (#9). The shell reads that label from the executable's embedded `FileDescription`, which the former build-once-then-rename flow left at the project default. `desktop-build.ps1` now publishes the WebView2 host **per app**, with identity split from the file name: `-p:AssemblyName=` gives a stable, always-valid file name (so the per-app build cache lookup is exact), while `-p:AssemblyTitle`/`-p:Product`=`` set the embedded `FileDescription` + `ProductName` the taskbar actually reads (the `.csproj` sets neither, so there's nothing to override, and the free-form app name never has to be a valid file name). Per-app exes are cached under `assets\build\wrapper-windows\\` (mtime-aware), so the trade vs the old single shared publish is one `dotnet publish` per app on first build. Validated on real Windows 11 hardware. (Note: the Windows shell caches taskbar identity per executable, so an existing app shows the new name only after its cache turns over; a clean install shows it immediately.) ## 0.1.0 - 2026-05-30 diff --git a/plugins/app-it-windows/skills/app-it-windows/templates/desktop-build.ps1 b/plugins/app-it-windows/skills/app-it-windows/templates/desktop-build.ps1 index b556026..cb450d5 100644 --- a/plugins/app-it-windows/skills/app-it-windows/templates/desktop-build.ps1 +++ b/plugins/app-it-windows/skills/app-it-windows/templates/desktop-build.ps1 @@ -82,16 +82,15 @@ $runTemplateEdge = Join-Path $ScriptDir 'run-template-edge.ps1' # Locate the wrapper-windows .csproj near these templates (step 2.2). Override # with APP_IT_WRAPPER_CSPROJ. Unlike the former single shared publish, each app # is published separately so its name can be baked into the assembly metadata -# (issue #9): -p:AssemblyName="" sets the embedded FileDescription / -# ProductName (they inherit from AssemblyName, which the .csproj leaves at its -# default), and those are what the Windows taskbar right-click reads. A shared -# binary can't carry per-app identity because the version resource is baked at -# compile time; rewriting it post-publish would need an external resource editor -# (rcedit), which the no-dependencies contract rules out. Per-app exes are cached -# under assets\build\wrapper-windows\\ and reused unless a source is newer. -$csproj = $null -$projDir = $null -$newestSrc = $null +# (issue #9). A shared binary can't carry per-app identity because the version +# resource is baked at compile time; rewriting it post-publish would need an +# external resource editor (rcedit), which the no-dependencies contract rules +# out. Per-app exes are cached under assets\build\wrapper-windows\\ and +# reused unless a host source file is newer. +$csproj = $null +$projDir = $null +$newestSrc = $null +$newestSrcUtc = [datetime]::MinValue if ($globalMode -eq 'webview2') { if ($env:APP_IT_WRAPPER_CSPROJ -and (Test-Path $env:APP_IT_WRAPPER_CSPROJ)) { $csproj = $env:APP_IT_WRAPPER_CSPROJ @@ -108,50 +107,55 @@ if ($globalMode -eq 'webview2') { $projDir = Split-Path -Parent $csproj $newestSrc = Get-ChildItem -Path $projDir -Recurse -Include *.cs,*.csproj,*.xaml -ErrorAction SilentlyContinue | Sort-Object LastWriteTimeUtc -Descending | Select-Object -First 1 + if ($newestSrc) { $newestSrcUtc = $newestSrc.LastWriteTimeUtc } } } # Publish the host for one app, baking the app name into the assembly so the -# taskbar right-click, FileDescription and OriginalFilename read the app name -# instead of "app-it-host" (issue #9). Cached per slug, mtime-aware. Returns the -# published ".exe" path, or $null on a build failure (the caller then -# falls that app back to the Edge launcher). +# taskbar right-click and FileDescription read the app name instead of +# "app-it-host" (issue #9). Identity is split from the file name on purpose: +# * -p:AssemblyName= -> the published file is ".exe". The slug is +# [a-z0-9-], so it's always a valid assembly/file name and the cache lookup +# below is exact - no MSBuild char-stripping to second-guess. +# * -p:AssemblyTitle / -p:Product = -> these set the embedded +# FileDescription + ProductName, which is what the Windows taskbar actually +# reads (verified on Windows 11). The .csproj sets neither, so nothing to +# override; the free-form app name never has to be a valid file name. +# The desktop copy step renames ".exe" to ".exe" for the visible +# file + process name. Cached per slug, mtime-aware. Build inputs are passed in +# rather than read from script scope. Returns the published ".exe" path, +# or $null on a build failure (the caller falls that app back to Edge). function Publish-HostForApp { - param([string]$AppName, [string]$Slug) - - $appPublishDir = Join-Path $Root "assets\build\wrapper-windows\$Slug" - $existing = if (Test-Path $appPublishDir) { - Get-ChildItem -Path $appPublishDir -Filter "$AppName.exe" -ErrorAction SilentlyContinue | Select-Object -First 1 - } else { $null } - $needsBuild = (-not $existing) -or ($newestSrc -and $newestSrc.LastWriteTimeUtc -gt $existing.LastWriteTimeUtc) + param( + [string]$AppName, + [string]$Slug, + [string]$Csproj, + [string]$RepoRoot, + [datetime]$NewestSrcUtc + ) + + $appPublishDir = Join-Path $RepoRoot "assets\build\wrapper-windows\$Slug" + $exePath = Join-Path $appPublishDir "$Slug.exe" + $existing = Get-Item -LiteralPath $exePath -ErrorAction SilentlyContinue + $needsBuild = (-not $existing) -or ($existing.LastWriteTimeUtc -lt $NewestSrcUtc) if ($needsBuild) { - Write-Host "Publishing WebView2 host for '$AppName': $csproj" - # -p:AssemblyName="" names the output .exe and, since - # the .csproj sets neither AssemblyTitle nor Product, cascades into the - # embedded FileDescription + ProductName (verified on Windows 11). The - # "AssemblyName=$AppName" token carries no literal space, so it survives - # native arg-passing under both pwsh 7 (Windows mode) and Windows - # PowerShell 5.1 (Legacy). Self-contained single-file per ADR 0005. - # Pipe to Out-Host: dotnet's stdout must not leak into the function's - # output stream, or the returned $exe path is polluted with build log - # lines. Out-Host keeps the progress visible while returning nothing. - & dotnet publish $csproj -c Release -r win-x64 --self-contained true ` + Write-Host "Publishing WebView2 host for '$AppName' (assembly: $Slug): $Csproj" + # AssemblyName=$Slug (no spaces/odd chars) and AssemblyTitle/Product=$AppName + # are each a single token whose only space comes from the variable value, + # so they survive native arg-passing under both pwsh 7 (Windows mode) and + # Windows PowerShell 5.1 (Legacy). Self-contained single-file per ADR 0005. + # Pipe to Out-Host so dotnet's stdout can't leak into this function's + # output stream and pollute the returned path; progress stays visible. + & dotnet publish $Csproj -c Release -r win-x64 --self-contained true ` -p:PublishSingleFile=true -p:IncludeNativeLibrariesForSelfExtract=true ` - -p:AssemblyName=$AppName ` + -p:AssemblyName=$Slug -p:AssemblyTitle=$AppName -p:Product=$AppName ` -o $appPublishDir | Out-Host if ($LASTEXITCODE -ne 0) { return $null } } - $exe = Get-ChildItem -Path $appPublishDir -Filter "$AppName.exe" -ErrorAction SilentlyContinue | + return Get-Item -LiteralPath $exePath -ErrorAction SilentlyContinue | Select-Object -First 1 -ExpandProperty FullName - if (-not $exe) { - # Name didn't map 1:1 (MSBuild stripped unusual chars) - take the - # largest .exe as the host. - $exe = Get-ChildItem -Path $appPublishDir -Filter *.exe -ErrorAction SilentlyContinue | - Sort-Object Length -Descending | Select-Object -First 1 -ExpandProperty FullName - } - return $exe } # --- Substitution helper ----------------------------------------------------- @@ -178,9 +182,12 @@ foreach ($app in $apps) { # into the assembly metadata (issue #9), so the taskbar entry, process name # AND the taskbar right-click all read the app, not "app-it-host". if ($appMode -eq 'webview2') { - $exe = Publish-HostForApp -AppName $app.name -Slug $app.slug + $exe = Publish-HostForApp -AppName $app.name -Slug $app.slug ` + -Csproj $csproj -RepoRoot $Root -NewestSrcUtc $newestSrcUtc if ($exe) { - Copy-Item -Force -Path $exe -Destination (Join-Path $appDir "$($app.name).exe") + # Rename .exe -> .exe so the visible file and process + # name are the app; the embedded FileDescription already carries it. + Copy-Item -Force -LiteralPath $exe -Destination (Join-Path $appDir "$($app.name).exe") } else { Write-Warning " dotnet publish failed for $($app.name) - falling back to the Edge --app launcher." $appMode = 'edge'