From 67bc7734f9787c6ed6c0ae3ba4d48beb85b2241c Mon Sep 17 00:00:00 2001 From: GrumpyGent <2617168+TheGrumpyGent@users.noreply.github.com> Date: Tue, 31 Mar 2026 14:28:24 -0400 Subject: [PATCH 01/14] Add MSIX installer design spec Documents the wapproj-based MSIX packaging approach for Microsoft Store distribution, including package identity, manifest structure, CI pipeline changes, and local build script updates. Co-Authored-By: Claude Sonnet 4.6 --- .../specs/2026-03-31-msix-installer-design.md | 158 ++++++++++++++++++ 1 file changed, 158 insertions(+) create mode 100644 docs/superpowers/specs/2026-03-31-msix-installer-design.md diff --git a/docs/superpowers/specs/2026-03-31-msix-installer-design.md b/docs/superpowers/specs/2026-03-31-msix-installer-design.md new file mode 100644 index 0000000..48f23c6 --- /dev/null +++ b/docs/superpowers/specs/2026-03-31-msix-installer-design.md @@ -0,0 +1,158 @@ +# MSIX Installer Design + +**Date:** 2026-03-31 +**Status:** Approved +**Distribution model:** Microsoft Store (primary) + GitHub Releases ZIP sideload (secondary) + +--- + +## Overview + +Add an MSIX installer to WindowSeat using a Windows Application Packaging Project (wapproj). The MSIX is built in CI and uploaded to the Microsoft Partner Center for Store distribution. GitHub Releases continues to distribute a plain ZIP for technical users who want to run the app without installing from the Store. + +--- + +## Package Identity + +| Field | Value | +|---|---| +| Name | `SteveBarnes.WindowSeat` | +| Publisher | `CN=4E8B8DF2-E078-41CF-871B-1CEF8CC0E1DC` | +| Version | Derived from git tag at build time (e.g. `v1.0.0` → `1.0.0.0`) | + +The publisher CN matches the Partner Center app registration. Microsoft signs the package during Store ingestion — no certificate management is required in this project. + +--- + +## Project Structure + +New files added under `packaging/WindowSeat.Package/`: + +``` +packaging/ +└── WindowSeat.Package/ + ├── WindowSeat.Package.wapproj ← Windows Application Packaging Project + ├── Package.appxmanifest ← Package identity, entry point, capabilities + └── Images/ + ├── Square44x44Logo.png ← Required (tray/taskbar size) + ├── Square150x150Logo.png ← Required (Start menu tile) + ├── Wide310x150Logo.png ← Required (wide Start tile) + └── StoreLogo.png ← Required (Partner Center listing) +``` + +`WindowSeat.sln` is updated to include `WindowSeat.Package.wapproj` as a solution project. The wapproj holds no code — it is a thin packaging wrapper that references `WindowSeat.App.csproj` as its entry point. + +--- + +## Package Manifest (Package.appxmanifest) + +### Identity and Properties + +```xml + +``` + +Version is injected at build time via MSBuild property (`/p:PackageVersion`). + +### Entry Point + +The app is declared as a full-trust desktop application via `EntryPoint="Windows.FullTrustApplication"` on the `Application` element: + +```xml + + + +``` + +This is the correct declaration for a wapproj-packaged WPF app. (`windows.fullTrustProcess` is a different extension used by UWP apps to spawn Win32 companion processes — it is not needed here.) + +The manifest also declares `TargetDeviceFamily` targeting Windows Desktop with a minimum version of `10.0.17763.0` (Windows 10 1809), consistent with the project's stated minimum. + +### Capabilities + +None declared. The app: +- Reads/writes `%AppData%\WindowSeat\` (implicit for packaged apps via AppData redirection) +- Writes a startup registry key under `HKCU` (no capability required) +- Calls Win32 hooks (`SetWinEventHook`) — no capability required +- Makes one outbound HTTPS call to the GitHub API (internet client capability is implicit for packaged desktop apps on Windows 10+) + +### Required Images + +Placeholder PNG assets are committed to `packaging/WindowSeat.Package/Images/`. These should be replaced with production-quality artwork before the first Store submission. + +| Asset | Size | +|---|---| +| Square44x44Logo.png | 44×44 | +| Square150x150Logo.png | 150×150 | +| Wide310x150Logo.png | 310×150 | +| StoreLogo.png | 50×50 | + +--- + +## CI/CD Pipeline Changes + +### release.yml + +Two additions alongside the existing flow: + +1. **Setup MSBuild** — adds `msbuild.exe` to PATH using the `microsoft/setup-msbuild` action. VS build tools including the Windows Application Packaging toolset are pre-installed on `windows-latest`. + +2. **Build wapproj** — MSBuild builds `packaging/WindowSeat.Package/WindowSeat.Package.wapproj` in Release/x64 with version injected: + ``` + msbuild packaging/WindowSeat.Package/WindowSeat.Package.wapproj + /p:Configuration=Release + /p:Platform=x64 + /p:PackageVersion={version}.0 + /p:AppxPackageSigningEnabled=false + ``` + `AppxPackageSigningEnabled=false` produces an unsigned MSIX suitable for Partner Center submission (Microsoft signs during ingestion). + +3. **Upload MSIX as workflow artifact** — the MSIX is uploaded via `actions/upload-artifact`, not attached to the public GitHub Release. This keeps the release page uncluttered for end users. The maintainer downloads it from the Actions run when submitting to Partner Center. + +### Existing ZIP flow (unchanged) + +`dotnet publish` → `Compress-Archive` → attached to GitHub Release. No changes. + +--- + +## Local Build Script (scripts/build.ps1) + +A `-Package` switch is added. When passed: + +1. Locates `msbuild.exe` via `vswhere.exe` (`${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\vswhere.exe`) +2. Builds the wapproj (same MSBuild arguments as CI) +3. Copies the resulting MSIX to `artifacts/` + +Without `-Package`, existing behaviour is unchanged. + +--- + +## Documentation Changes (docs/user-guide.md) + +The installation section is updated to reflect the new distribution model: + +- **Recommended:** Install from the Microsoft Store (one click, automatic updates, clean uninstall via Settings → Apps → Installed apps) +- **Sideload (technical users):** Download the ZIP from GitHub Releases, extract, and run `WindowSeat.exe` directly. No Add/Remove Programs entry; uninstall by deleting the folder and optionally removing `%AppData%\WindowSeat\`. + +--- + +## Out of Scope + +- Code signing for the GitHub ZIP or MSIX sideload (deferred — add a trusted cert to the signing step in `release.yml` when available) +- MSIX bundle (`.msixbundle`) for multi-architecture support — single x64 package is sufficient for v1.0 +- Auto-update via Store — the existing GitHub API update check remains in place; Store handles its own updates independently + +--- + +## Future: Adding Signed GitHub Releases (Option B) + +When a trusted code-signing certificate becomes available (e.g. SignPath.io OSS program): + +1. Store the PFX as a base64-encoded GitHub Actions secret (`SIGNING_CERT_PFX`) and its password as `SIGNING_CERT_PASSWORD` +2. Add a `signtool.exe` step after the wapproj build in `release.yml` +3. Attach the signed MSIX to the GitHub Release alongside the ZIP + +No structural changes to the wapproj, manifest, or local build script are required. From fca036e60b74a44819e788a16938f8e4f353728a Mon Sep 17 00:00:00 2001 From: GrumpyGent <2617168+TheGrumpyGent@users.noreply.github.com> Date: Tue, 31 Mar 2026 14:47:00 -0400 Subject: [PATCH 02/14] Add MSIX installer implementation plan Step-by-step plan for wapproj setup, manifest, CI pipeline changes, local build script, and documentation updates. Co-Authored-By: Claude Sonnet 4.6 --- .../plans/2026-03-31-msix-installer.md | 601 ++++++++++++++++++ 1 file changed, 601 insertions(+) create mode 100644 docs/superpowers/plans/2026-03-31-msix-installer.md diff --git a/docs/superpowers/plans/2026-03-31-msix-installer.md b/docs/superpowers/plans/2026-03-31-msix-installer.md new file mode 100644 index 0000000..8f36377 --- /dev/null +++ b/docs/superpowers/plans/2026-03-31-msix-installer.md @@ -0,0 +1,601 @@ +# MSIX Installer Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Add a Windows Application Packaging Project (wapproj) that produces an unsigned MSIX for Microsoft Store submission, while keeping the existing GitHub Releases ZIP distribution unchanged. + +**Architecture:** A `.wapproj` in `packaging/WindowSeat.Package/` wraps `WindowSeat.App.csproj` and declares the package identity, entry point, and assets in `Package.appxmanifest`. CI builds the wapproj with MSBuild and uploads the MSIX as a workflow artifact for manual Partner Center submission. The existing `dotnet publish` → ZIP → GitHub Release flow is untouched. + +**Tech Stack:** MSBuild (wapproj toolset, pre-installed in VS 2022), `microsoft/setup-msbuild` GitHub Action, PowerShell 7 (manifest version patching, image generation), `actions/upload-artifact@v4` + +--- + +## File Map + +**Create:** +- `packaging/WindowSeat.Package/Images/Square44x44Logo.png` — 44×44 placeholder tile asset +- `packaging/WindowSeat.Package/Images/Square150x150Logo.png` — 150×150 placeholder tile asset +- `packaging/WindowSeat.Package/Images/Wide310x150Logo.png` — 310×150 placeholder tile asset +- `packaging/WindowSeat.Package/Images/StoreLogo.png` — 50×50 placeholder Store logo +- `packaging/WindowSeat.Package/Package.appxmanifest` — package identity, entry point, capabilities +- `packaging/WindowSeat.Package/WindowSeat.Package.wapproj` — Windows Application Packaging Project + +**Modify:** +- `WindowSeat.sln` — add wapproj as a solution project +- `.github/workflows/release.yml` — Setup MSBuild, patch manifest version, build wapproj, upload MSIX artifact +- `scripts/build.ps1` — extend `-Package` switch to also produce an MSIX via MSBuild +- `docs/user-guide.md` — update Installation and Uninstalling sections for the Store/ZIP model + +--- + +### Task 1: Create packaging directory and placeholder images + +> The MSIX manifest references four image assets. They must exist at the declared sizes or the wapproj build will fail. These are placeholders — replace with real artwork before the first Store submission. + +**Files:** +- Create: `packaging/WindowSeat.Package/Images/Square44x44Logo.png` +- Create: `packaging/WindowSeat.Package/Images/Square150x150Logo.png` +- Create: `packaging/WindowSeat.Package/Images/Wide310x150Logo.png` +- Create: `packaging/WindowSeat.Package/Images/StoreLogo.png` + +- [ ] **Step 1: Create the directory and generate placeholder PNGs** + +Run this PowerShell script from the repo root. It creates solid-colour PNGs at the exact sizes required by the manifest: + +```powershell +New-Item -ItemType Directory -Force -Path packaging/WindowSeat.Package/Images | Out-Null + +Add-Type -AssemblyName System.Drawing + +function New-PlaceholderPng { + param([string]$Path, [int]$Width, [int]$Height) + $bmp = New-Object System.Drawing.Bitmap($Width, $Height) + $g = [System.Drawing.Graphics]::FromImage($bmp) + $g.Clear([System.Drawing.Color]::FromArgb(255, 92, 45, 145)) # #5C2D91 purple + $g.Dispose() + $bmp.Save($Path, [System.Drawing.Imaging.ImageFormat]::Png) + $bmp.Dispose() +} + +New-PlaceholderPng -Path "packaging/WindowSeat.Package/Images/Square44x44Logo.png" -Width 44 -Height 44 +New-PlaceholderPng -Path "packaging/WindowSeat.Package/Images/Square150x150Logo.png" -Width 150 -Height 150 +New-PlaceholderPng -Path "packaging/WindowSeat.Package/Images/Wide310x150Logo.png" -Width 310 -Height 150 +New-PlaceholderPng -Path "packaging/WindowSeat.Package/Images/StoreLogo.png" -Width 50 -Height 50 +``` + +- [ ] **Step 2: Verify the files exist at the right sizes** + +```powershell +Get-ChildItem packaging/WindowSeat.Package/Images/ | Select-Object Name, Length +``` + +Expected: four `.png` files, each non-zero bytes. + +- [ ] **Step 3: Commit** + +```bash +git add packaging/WindowSeat.Package/Images/ +git commit -m "feat(packaging): add placeholder MSIX image assets" +``` + +--- + +### Task 2: Create Package.appxmanifest + +> The manifest declares the package identity (must match Partner Center exactly), the entry point, the target OS range, and the `runFullTrust` restricted capability (required whenever `EntryPoint="Windows.FullTrustApplication"` is used). + +**Files:** +- Create: `packaging/WindowSeat.Package/Package.appxmanifest` + +- [ ] **Step 1: Create the file** + +Create `packaging/WindowSeat.Package/Package.appxmanifest` with this exact content: + +```xml + + + + + + + WindowSeat + SteveBarnes + Images\StoreLogo.png + + + + + + + + + + + + + + + + + + + + + + + +``` + +- [ ] **Step 2: Commit** + +```bash +git add packaging/WindowSeat.Package/Package.appxmanifest +git commit -m "feat(packaging): add MSIX package manifest" +``` + +--- + +### Task 3: Create WindowSeat.Package.wapproj + +> The wapproj is an MSBuild project that ties together the manifest, image assets, and the reference to `WindowSeat.App.csproj`. It uses the Desktop Bridge MSBuild targets installed with Visual Studio's "Windows application development" workload. +> +> **Prerequisite:** Visual Studio 2022 must be installed with the **"Windows application development"** workload (provides `Microsoft.DesktopBridge.props` and `Microsoft.DesktopBridge.targets`). On GitHub Actions `windows-latest`, these are present via the "Universal Windows Platform development" workload. + +**Files:** +- Create: `packaging/WindowSeat.Package/WindowSeat.Package.wapproj` + +- [ ] **Step 1: Create the file** + +Create `packaging/WindowSeat.Package/WindowSeat.Package.wapproj` with this exact content: + +```xml + + + + + + + Debug + x86 + {D1E2F3A4-0005-0005-0005-000000000005} + {C7167F0D-BC9F-4E6A-9F78-5F2B13F8E92E};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + Package + false + false + Never + false + 10.0.22621.0 + 10.0.17763.0 + + + + pdbonly + false + bin\x64\Debug\AppPackages\ + + + + none + true + bin\x64\Release\AppPackages\ + + + + + Designer + + + + + + + + + + + + + true + + + + + + + +``` + +- [ ] **Step 2: Commit** + +```bash +git add packaging/WindowSeat.Package/WindowSeat.Package.wapproj +git commit -m "feat(packaging): add Windows Application Packaging Project" +``` + +--- + +### Task 4: Add wapproj to WindowSeat.sln + +> The solution file must reference the wapproj so Visual Studio can open it and so `dotnet build WindowSeat.sln` is aware of it. The wapproj uses a specific project type GUID (`{C7167F0D-...}`) that Visual Studio recognises as a packaging project. +> +> **Note:** `dotnet build` on the solution will skip the wapproj (it requires MSBuild, not the .NET CLI). This is expected and fine — the wapproj is only built explicitly via `msbuild`. + +**Files:** +- Modify: `WindowSeat.sln` + +- [ ] **Step 1: Add the project declaration** + +In `WindowSeat.sln`, insert this block immediately before the `Global` keyword (after the last `EndProject` line): + +``` +Project("{C7167F0D-BC9F-4E6A-9F78-5F2B13F8E92E}") = "WindowSeat.Package", "packaging\WindowSeat.Package\WindowSeat.Package.wapproj", "{D1E2F3A4-0005-0005-0005-000000000005}" +EndProject +``` + +- [ ] **Step 2: Add build configuration mappings** + +In `WindowSeat.sln`, inside `GlobalSection(ProjectConfigurationPlatforms) = postSolution`, add these four lines (before `EndGlobalSection`): + +``` + {D1E2F3A4-0005-0005-0005-000000000005}.Debug|Any CPU.ActiveCfg = Debug|x64 + {D1E2F3A4-0005-0005-0005-000000000005}.Debug|Any CPU.Build.0 = Debug|x64 + {D1E2F3A4-0005-0005-0005-000000000005}.Release|Any CPU.ActiveCfg = Release|x64 + {D1E2F3A4-0005-0005-0005-000000000005}.Release|Any CPU.Build.0 = Release|x64 +``` + +- [ ] **Step 3: Commit** + +```bash +git add WindowSeat.sln +git commit -m "feat(packaging): add WindowSeat.Package to solution" +``` + +--- + +### Task 5: Verify local wapproj build + +> Before touching CI, verify the wapproj builds on the local machine. This catches manifest errors, missing VS workloads, and path issues early. +> +> **Requires:** Visual Studio 2022 with the "Windows application development" workload installed. + +- [ ] **Step 1: Locate MSBuild** + +```powershell +$vswhere = "${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\vswhere.exe" +$msbuild = & $vswhere -latest -requires Microsoft.Component.MSBuild ` + -find MSBuild\**\Bin\MSBuild.exe | Select-Object -First 1 +Write-Host "MSBuild: $msbuild" +``` + +Expected: a path like `C:\Program Files\Microsoft Visual Studio\2022\...\MSBuild\Current\Bin\MSBuild.exe` + +If `vswhere.exe` is not found or returns nothing, install the "Windows application development" workload in the Visual Studio Installer and retry. + +- [ ] **Step 2: Build the wapproj** + +```powershell +& $msbuild packaging/WindowSeat.Package/WindowSeat.Package.wapproj ` + /p:Configuration=Release ` + /p:Platform=x64 ` + /p:AppxPackageSigningEnabled=false ` + /p:AppxAutoIncrementPackageRevision=false ` + /p:GenerateTestArtifacts=false +``` + +Expected: `Build succeeded.` with no errors. Warnings about placeholder images are fine. + +- [ ] **Step 3: Verify the MSIX was produced** + +```powershell +Get-ChildItem packaging/WindowSeat.Package/bin/x64/Release/AppPackages/ -Recurse -Filter *.msix +``` + +Expected: one `.msix` file (e.g. `WindowSeat.Package_1.0.0.0_x64.msix`). + +- [ ] **Step 4: Inspect the MSIX contents (optional sanity check)** + +An MSIX is a zip file. Rename it to `.zip` and open it, or use: + +```powershell +$msix = Get-ChildItem packaging/WindowSeat.Package/bin/x64/Release/AppPackages/ -Recurse -Filter *.msix | Select-Object -First 1 +$zip = $msix.FullName -replace '\.msix$', '.zip' +Copy-Item $msix.FullName $zip +Expand-Archive $zip -DestinationPath "$($msix.Directory)\msix-contents" -Force +Get-ChildItem "$($msix.Directory)\msix-contents" | Select-Object Name +``` + +Expected to see: `WindowSeat.exe`, `AppxManifest.xml`, `AppxBlockMap.xml`, image files, and the app's DLLs. + +--- + +### Task 6: Update release.yml + +> Add three things to the CD pipeline: (1) `setup-msbuild` action so `msbuild.exe` is on PATH, (2) a PowerShell step that patches the manifest version from the git tag before building, (3) an MSBuild step to build the wapproj, and (4) an `upload-artifact` step to preserve the MSIX for Partner Center submission. The MSIX is NOT attached to the GitHub Release — it is a workflow artifact only. + +**Files:** +- Modify: `.github/workflows/release.yml` + +- [ ] **Step 1: Add `setup-msbuild` after the `Setup .NET 8` step** + +In `.github/workflows/release.yml`, after the `Setup .NET 8` step block, insert: + +```yaml + - name: Setup MSBuild + uses: microsoft/setup-msbuild@v2 +``` + +- [ ] **Step 2: Add manifest version-patch step after "Publish app"** + +After the `Publish app` step block, insert: + +```yaml + # ── MSIX packaging ──────────────────────────────────────────────────── + - name: Patch manifest version + shell: pwsh + run: | + $v = "${{ steps.version.outputs.VERSION }}.0" + $path = "packaging/WindowSeat.Package/Package.appxmanifest" + (Get-Content $path -Raw) ` + -replace '(?<=]*Version=")[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+', $v | + Set-Content $path + Write-Host "Manifest version set to $v" +``` + +- [ ] **Step 3: Add MSBuild wapproj build step** + +Immediately after the manifest-patch step, insert: + +```yaml + - name: Build MSIX package + shell: pwsh + run: | + msbuild packaging/WindowSeat.Package/WindowSeat.Package.wapproj ` + /p:Configuration=Release ` + /p:Platform=x64 ` + /p:AppxPackageSigningEnabled=false ` + /p:AppxAutoIncrementPackageRevision=false ` + /p:GenerateTestArtifacts=false ` + "/p:AppxPackageDir=${{ github.workspace }}\artifacts\msix\\" +``` + +- [ ] **Step 4: Add upload-artifact step** + +Immediately after the `Build MSIX package` step, insert: + +```yaml + - name: Upload MSIX (Store submission artifact) + uses: actions/upload-artifact@v4 + with: + name: WindowSeat-${{ steps.version.outputs.VERSION }}-store-package + path: artifacts/msix/*.msix +``` + +- [ ] **Step 5: Update the TODO comment** + +Replace the existing TODO comment block in `release.yml`: + +```yaml + # ── MSIX packaging ──────────────────────────────────────────────────── + # TODO: Wire up Windows Application Packaging Project (wapproj) here + # once packaging/ scaffold is complete. For v1.0, zip the publish output + # as a stopgap so users can still download and run. +``` + +with just the section heading (the actual steps are now the ones added above): + +```yaml + # ── ZIP (GitHub Releases sideload) ──────────────────────────────────── +``` + +- [ ] **Step 6: Commit** + +```bash +git add .github/workflows/release.yml +git commit -m "feat(ci): build MSIX via wapproj and upload as Store submission artifact" +``` + +--- + +### Task 7: Update build.ps1 + +> Extend the existing `-Package` switch so that, after producing the ZIP, it also locates MSBuild and builds the wapproj to produce a local MSIX in `artifacts/`. If `vswhere.exe` or the VS workload is absent the script prints a clear warning and skips MSIX (not a hard failure — the ZIP is still produced). + +**Files:** +- Modify: `scripts/build.ps1` + +- [ ] **Step 1: Add `$ArtifactsDir` variable to the existing variable block** + +In `scripts/build.ps1`, after the `$TestProject` line, add: + +```powershell +$ArtifactsDir = Join-Path $RepoRoot 'artifacts' +$WaprojFile = Join-Path $RepoRoot 'packaging\WindowSeat.Package\WindowSeat.Package.wapproj' +``` + +- [ ] **Step 2: Replace the existing `-Package` block** + +Replace the existing `if ($Package)` block (lines 58–73) with: + +```powershell +# ── Publish ─────────────────────────────────────────────────────────────────── +if ($Package) { + Write-Step "Publishing" + if (Test-Path $PublishDir) { Remove-Item $PublishDir -Recurse -Force } + + dotnet publish (Join-Path $RepoRoot 'src/WindowSeat.App/WindowSeat.App.csproj') ` + --configuration $Configuration ` + --runtime win-x64 ` + --self-contained false ` + --output $PublishDir + if ($LASTEXITCODE -ne 0) { throw "Publish failed." } + + Write-Step "Creating ZIP" + $zipPath = Join-Path $RepoRoot "WindowSeat-local-win-x64.zip" + Compress-Archive -Path "$PublishDir/*" -DestinationPath $zipPath -Force + Write-Host "ZIP created: $zipPath" -ForegroundColor Green + + # ── MSIX (requires VS 2022 with Windows application development workload) ── + Write-Step "Building MSIX package" + $vswhere = "${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\vswhere.exe" + if (-not (Test-Path $vswhere)) { + Write-Warning "vswhere.exe not found — skipping MSIX. Install Visual Studio 2022 to enable local MSIX packaging." + } else { + $msbuild = & $vswhere -latest -requires Microsoft.Component.MSBuild ` + -find MSBuild\**\Bin\MSBuild.exe | Select-Object -First 1 + if (-not $msbuild) { + Write-Warning "MSBuild not found via vswhere — skipping MSIX." + } else { + $msixOut = Join-Path $ArtifactsDir 'msix' + if (Test-Path $msixOut) { Remove-Item $msixOut -Recurse -Force } + New-Item -ItemType Directory -Force -Path $msixOut | Out-Null + + & $msbuild $WaprojFile ` + /p:Configuration=$Configuration ` + /p:Platform=x64 ` + /p:AppxPackageSigningEnabled=false ` + /p:AppxAutoIncrementPackageRevision=false ` + /p:GenerateTestArtifacts=false ` + "/p:AppxPackageDir=$msixOut\\" + if ($LASTEXITCODE -ne 0) { throw "MSIX build failed." } + + $msix = Get-ChildItem $msixOut -Filter *.msix | Select-Object -First 1 + Write-Host "MSIX created: $($msix.FullName)" -ForegroundColor Green + } + } +} +``` + +- [ ] **Step 3: Verify the script runs cleanly without `-Package`** + +```powershell +.\scripts\build.ps1 -SkipTests +``` + +Expected: `Build complete.` with no errors. + +- [ ] **Step 4: Verify the script produces both ZIP and MSIX with `-Package`** + +```powershell +.\scripts\build.ps1 -Package -SkipTests +``` + +Expected: `ZIP created: ...` and `MSIX created: ...` (or a warning about missing VS workload if not installed). + +- [ ] **Step 5: Commit** + +```bash +git add scripts/build.ps1 +git commit -m "feat(build): add local MSIX packaging to build.ps1 -Package switch" +``` + +--- + +### Task 8: Update docs/user-guide.md + +> Replace the current GitHub-MSIX install instructions with the new Store-primary / ZIP-sideload model. The Uninstalling section already covers the Store path correctly; add the ZIP uninstall path. + +**Files:** +- Modify: `docs/user-guide.md` + +- [ ] **Step 1: Replace the Installation section** + +Replace the current `## Installation` section (lines 23–29) with: + +```markdown +## Installation + +### Microsoft Store (Recommended) + +1. Open the **Microsoft Store** on your PC +2. Search for **WindowSeat** or follow the link from the [GitHub Releases page](https://github.com/stmba/WindowSeat/releases) +3. Click **Get** (or **Install**) +4. WindowSeat starts automatically — look for the 🪑 icon in your system tray + +The Store version updates automatically and can be uninstalled cleanly from Settings → Apps. + +### ZIP Download (Technical Users / Sideload) + +If you prefer not to use the Store: + +1. Go to the [Releases page](https://github.com/stmba/WindowSeat/releases) on GitHub +2. Download the latest `WindowSeat-win-x64.zip` +3. Extract the ZIP to a folder of your choice (e.g. `C:\Program Files\WindowSeat\`) +4. Run `WindowSeat.exe` +5. Look for the 🪑 icon in your system tray + +> **Note:** The ZIP version has no installer. There is no entry in Add/Remove Programs. Auto-start at login works via the in-app Settings toggle as normal. +``` + +- [ ] **Step 2: Replace the Uninstalling section** + +Replace the current `## Uninstalling` section (lines 93–98) with: + +```markdown +## Uninstalling + +### Store version + +1. Open **Settings → Apps → Installed apps** +2. Search for **WindowSeat** +3. Click the three-dot menu and choose **Uninstall** + +### ZIP version + +1. Right-click the 🪑 tray icon and choose **Exit** +2. Delete the folder you extracted WindowSeat into +3. If you enabled auto-start, open **Task Manager → Startup apps** and remove the WindowSeat entry (or toggle it off in Settings before exiting) + +In both cases, WindowSeat's settings file at `%AppData%\WindowSeat\settings.json` is not removed automatically. Delete that folder manually if you want a complete clean removal. +``` + +- [ ] **Step 3: Commit** + +```bash +git add docs/user-guide.md +git commit -m "docs: update installation and uninstall instructions for Store/ZIP model" +``` + +--- + +## Self-Review + +**Spec coverage check:** + +| Spec requirement | Task | +|---|---| +| wapproj in `packaging/WindowSeat.Package/` | Task 3 | +| Package identity: `SteveBarnes.WindowSeat` / `CN=4E8B8DF2-...` | Task 2 | +| Entry point: `Windows.FullTrustApplication` | Task 2 | +| `runFullTrust` capability | Task 2 | +| `TargetDeviceFamily` min `10.0.17763.0` | Task 2 | +| Placeholder image assets at correct sizes | Task 1 | +| wapproj added to `WindowSeat.sln` | Task 4 | +| CI: `setup-msbuild` action | Task 6 | +| CI: manifest version patched from git tag | Task 6 | +| CI: wapproj built with `AppxPackageSigningEnabled=false` | Task 6 | +| CI: MSIX uploaded as workflow artifact (not GitHub Release attachment) | Task 6 | +| CI: ZIP flow unchanged | Task 6 (no-touch) | +| `build.ps1 -Package` builds MSIX locally | Task 7 | +| `build.ps1` gracefully skips MSIX if VS not installed | Task 7 | +| `docs/user-guide.md` reflects Store/ZIP model | Task 8 | +| Future signed-release path documented | Spec only (no code needed) | + +All spec requirements covered. No placeholders in any task. From 366dfebe1f1ce1c139bef1190d5e9c68c14035c8 Mon Sep 17 00:00:00 2001 From: GrumpyGent <2617168+TheGrumpyGent@users.noreply.github.com> Date: Tue, 31 Mar 2026 14:50:25 -0400 Subject: [PATCH 03/14] chore: ignore .worktrees/ directory --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 0522e70..c3c951f 100644 --- a/.gitignore +++ b/.gitignore @@ -56,6 +56,9 @@ coverage/ Thumbs.db Desktop.ini +# Git worktrees +.worktrees/ + # Secrets — never commit these *.pfx *.p12 From ab5dbe118caad03c35774f6eea788a462d9346f9 Mon Sep 17 00:00:00 2001 From: GrumpyGent <2617168+TheGrumpyGent@users.noreply.github.com> Date: Tue, 31 Mar 2026 16:05:52 -0400 Subject: [PATCH 04/14] chore: migrate to .NET 10 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Update all project TFMs from net8.0-windows to net10.0-windows - Update CI workflows to setup-dotnet 10.0.x - Remove redundant System.Text.Json package reference (inbox in .NET 10) - Fix PlaceholderText (WinUI-only) → ToolTip on ExclusionTextBox - Fix WindowMover constructor call (drop spurious settingsService arg) - Add placeholder windowseat.ico (was missing from repo) - Update docs to reference .NET 10 SDK Co-Authored-By: Claude Sonnet 4.6 --- .github/workflows/build.yml | 4 ++-- .github/workflows/release.yml | 4 ++-- README.md | 2 +- docs/WindowSeat-Scope.md | 2 +- docs/contributing.md | 2 +- src/WindowSeat.App/App.xaml.cs | 2 +- src/WindowSeat.App/Assets/windowseat.ico | Bin 0 -> 766 bytes src/WindowSeat.App/Views/SettingsWindow.xaml | 2 +- src/WindowSeat.App/WindowSeat.App.csproj | 2 +- src/WindowSeat.Core/WindowSeat.Core.csproj | 2 +- .../WindowSeat.Settings.csproj | 5 +---- src/WindowSeat.Tests/WindowSeat.Tests.csproj | 2 +- 12 files changed, 13 insertions(+), 16 deletions(-) create mode 100644 src/WindowSeat.App/Assets/windowseat.ico diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 7800ede..4d408a1 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -14,10 +14,10 @@ jobs: - name: Checkout uses: actions/checkout@v4 - - name: Setup .NET 8 + - name: Setup .NET 10 uses: actions/setup-dotnet@v4 with: - dotnet-version: '8.0.x' + dotnet-version: '10.0.x' - name: Restore dependencies run: dotnet restore WindowSeat.sln diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index f4debc5..f6d319c 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -13,10 +13,10 @@ jobs: - name: Checkout uses: actions/checkout@v4 - - name: Setup .NET 8 + - name: Setup .NET 10 uses: actions/setup-dotnet@v4 with: - dotnet-version: '8.0.x' + dotnet-version: '10.0.x' - name: Extract version from tag id: version diff --git a/README.md b/README.md index 1181d08..8db06bb 100644 --- a/README.md +++ b/README.md @@ -59,7 +59,7 @@ For advanced configuration see [docs/power-user-guide.md](docs/power-user-guide. ## Building from Source **Prerequisites** -- [.NET 8 SDK](https://dotnet.microsoft.com/download/dotnet/8.0) +- [.NET 10 SDK](https://dotnet.microsoft.com/download/dotnet/10.0) - Visual Studio 2022 (17.8+) with the **Windows application development** workload, or VS Code with the C# Dev Kit extension **Clone and build** diff --git a/docs/WindowSeat-Scope.md b/docs/WindowSeat-Scope.md index e1de308..10d9da4 100644 --- a/docs/WindowSeat-Scope.md +++ b/docs/WindowSeat-Scope.md @@ -4,7 +4,7 @@ **Last Updated:** 2026-03-30 **License:** MIT **Target Platform:** Windows 11 (primary), Windows 10 22H2+ (best effort) -**Stack:** .NET 8 LTS, C#, WPF, PowerShell (build/packaging tooling) +**Stack:** .NET 10, C#, WPF, PowerShell (build/packaging tooling) --- diff --git a/docs/contributing.md b/docs/contributing.md index 9903e70..98de1f3 100644 --- a/docs/contributing.md +++ b/docs/contributing.md @@ -6,7 +6,7 @@ Thank you for your interest in contributing. This document covers everything you ## Prerequisites -- [.NET 8 SDK](https://dotnet.microsoft.com/download/dotnet/8.0) +- [.NET 10 SDK](https://dotnet.microsoft.com/download/dotnet/10.0) - Visual Studio 2022 (17.8+) with the **Windows application development** workload _or_ VS Code with the [C# Dev Kit](https://marketplace.visualstudio.com/items?itemName=ms-dotnettools.csdevkit) extension - Git diff --git a/src/WindowSeat.App/App.xaml.cs b/src/WindowSeat.App/App.xaml.cs index da5560b..1f8093c 100644 --- a/src/WindowSeat.App/App.xaml.cs +++ b/src/WindowSeat.App/App.xaml.cs @@ -31,7 +31,7 @@ protected override async void OnStartup(StartupEventArgs e) ? new CursorMonitorDetector() : new TaskbarMonitorDetector(); - _mover = new WindowMover(_settingsService, detector); + _mover = new WindowMover(detector); _listener = new WindowEventListener(); _listener.WindowShown += OnWindowShown; _listener.Start(); diff --git a/src/WindowSeat.App/Assets/windowseat.ico b/src/WindowSeat.App/Assets/windowseat.ico new file mode 100644 index 0000000000000000000000000000000000000000..f4a7273c44bdfe62f2521547adbdb749b956f8f5 GIT binary patch literal 766 zcmeH_u@!(Y2t+Tw3}9tVnK4|#eHe{#+yXz^rnk_8fADf$9#W&sR+Y|0+L!|jc4+Fj iA&L7la8__d1VP@d%-VpeC+zt3=^x-6UeyJ;7QmJO literal 0 HcmV?d00001 diff --git a/src/WindowSeat.App/Views/SettingsWindow.xaml b/src/WindowSeat.App/Views/SettingsWindow.xaml index 70ac87c..eb00414 100644 --- a/src/WindowSeat.App/Views/SettingsWindow.xaml +++ b/src/WindowSeat.App/Views/SettingsWindow.xaml @@ -129,7 +129,7 @@ - net8.0-windows + net10.0-windows true enable enable diff --git a/src/WindowSeat.Core/WindowSeat.Core.csproj b/src/WindowSeat.Core/WindowSeat.Core.csproj index 7bd7350..0b6c815 100644 --- a/src/WindowSeat.Core/WindowSeat.Core.csproj +++ b/src/WindowSeat.Core/WindowSeat.Core.csproj @@ -1,6 +1,6 @@ - net8.0-windows + net10.0-windows enable enable latest diff --git a/src/WindowSeat.Settings/WindowSeat.Settings.csproj b/src/WindowSeat.Settings/WindowSeat.Settings.csproj index f023c72..b89e89d 100644 --- a/src/WindowSeat.Settings/WindowSeat.Settings.csproj +++ b/src/WindowSeat.Settings/WindowSeat.Settings.csproj @@ -1,13 +1,10 @@ - net8.0-windows + net10.0-windows enable enable latest WindowSeat.Settings WindowSeat.Settings - - - diff --git a/src/WindowSeat.Tests/WindowSeat.Tests.csproj b/src/WindowSeat.Tests/WindowSeat.Tests.csproj index 0385cdd..5a539bc 100644 --- a/src/WindowSeat.Tests/WindowSeat.Tests.csproj +++ b/src/WindowSeat.Tests/WindowSeat.Tests.csproj @@ -1,6 +1,6 @@ - net8.0-windows + net10.0-windows enable enable latest From 9557db2e34b66e428f99608c4b9f01a794249d56 Mon Sep 17 00:00:00 2001 From: GrumpyGent <2617168+TheGrumpyGent@users.noreply.github.com> Date: Tue, 31 Mar 2026 16:10:04 -0400 Subject: [PATCH 05/14] feat(packaging): add placeholder MSIX image assets --- .../Images/Square150x150Logo.png | Bin 0 -> 514 bytes .../WindowSeat.Package/Images/Square44x44Logo.png | Bin 0 -> 188 bytes packaging/WindowSeat.Package/Images/StoreLogo.png | Bin 0 -> 196 bytes .../WindowSeat.Package/Images/Wide310x150Logo.png | Bin 0 -> 627 bytes 4 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 packaging/WindowSeat.Package/Images/Square150x150Logo.png create mode 100644 packaging/WindowSeat.Package/Images/Square44x44Logo.png create mode 100644 packaging/WindowSeat.Package/Images/StoreLogo.png create mode 100644 packaging/WindowSeat.Package/Images/Wide310x150Logo.png diff --git a/packaging/WindowSeat.Package/Images/Square150x150Logo.png b/packaging/WindowSeat.Package/Images/Square150x150Logo.png new file mode 100644 index 0000000000000000000000000000000000000000..f6df41ec4124ee1ff3db0e4307cc00bc6c20ffac GIT binary patch literal 514 zcmeAS@N?(olHy`uVBq!ia0vp^(?FPm4M^HB7Cr(}jKx9jP7LeL$-D$|SkfJR9T^xl z_H+M9WCij$3p^r=85sBugD~Uq{1quc4bwec978JN-d<8NS%G}c0*}aI1_r*vAk26?e?cc>0 literal 0 HcmV?d00001 diff --git a/packaging/WindowSeat.Package/Images/StoreLogo.png b/packaging/WindowSeat.Package/Images/StoreLogo.png new file mode 100644 index 0000000000000000000000000000000000000000..e8b5c0bff1f1f746a3a2b04df8d44dbdfad1013e GIT binary patch literal 196 zcmeAS@N?(olHy`uVBq!ia0vp^Mj*_=1|;R|J2nC-#^NA%Cx&(BWL^R}Ea{HEjtmSN z`?>!lvI6;>1s;*b3=DjSL74G){)!Z!V5FyuV@QVc+w+Ek2NW0%IPg4Nu`Gb6T9oBo zd;F%5jgR z3=A9lx&I`x0{NT;9+AZi419+{nDKc2iWCM0CJs**$B>G+w^s~#859@}81Ovg2-awm zXV4>%?-nzhq!r Date: Tue, 31 Mar 2026 16:12:09 -0400 Subject: [PATCH 06/14] feat(packaging): add MSIX package manifest --- .../WindowSeat.Package/Package.appxmanifest | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 packaging/WindowSeat.Package/Package.appxmanifest diff --git a/packaging/WindowSeat.Package/Package.appxmanifest b/packaging/WindowSeat.Package/Package.appxmanifest new file mode 100644 index 0000000..60b496a --- /dev/null +++ b/packaging/WindowSeat.Package/Package.appxmanifest @@ -0,0 +1,48 @@ + + + + + + + WindowSeat + SteveBarnes + Images\StoreLogo.png + + + + + + + + + + + + + + + + + + + + + + + From 4afac965d1a6da5ed4e70b7baae2b3f07cd0a8f9 Mon Sep 17 00:00:00 2001 From: GrumpyGent <2617168+TheGrumpyGent@users.noreply.github.com> Date: Tue, 31 Mar 2026 16:16:43 -0400 Subject: [PATCH 07/14] feat(packaging): add Windows Application Packaging Project --- .../WindowSeat.Package.wapproj | 56 +++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 packaging/WindowSeat.Package/WindowSeat.Package.wapproj diff --git a/packaging/WindowSeat.Package/WindowSeat.Package.wapproj b/packaging/WindowSeat.Package/WindowSeat.Package.wapproj new file mode 100644 index 0000000..096032e --- /dev/null +++ b/packaging/WindowSeat.Package/WindowSeat.Package.wapproj @@ -0,0 +1,56 @@ + + + + + + + Debug + x86 + {D1E2F3A4-0005-0005-0005-000000000005} + {C7167F0D-BC9F-4E6A-9F78-5F2B13F8E92E};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + Package + false + false + Never + false + 10.0.22621.0 + 10.0.17763.0 + + + + pdbonly + false + bin\x64\Debug\AppPackages\ + + + + none + true + bin\x64\Release\AppPackages\ + + + + + Designer + + + + + + + + + + + + + true + + + + + + + From 9a31a3c47b3533344f4d92bc2439cf973ffd34eb Mon Sep 17 00:00:00 2001 From: GrumpyGent <2617168+TheGrumpyGent@users.noreply.github.com> Date: Tue, 31 Mar 2026 16:21:35 -0400 Subject: [PATCH 08/14] feat(packaging): add WindowSeat.Package to solution --- WindowSeat.sln | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/WindowSeat.sln b/WindowSeat.sln index 9502d1a..cd0487c 100644 --- a/WindowSeat.sln +++ b/WindowSeat.sln @@ -10,6 +10,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WindowSeat.Settings", "src\ EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WindowSeat.Tests", "src\WindowSeat.Tests\WindowSeat.Tests.csproj", "{A1B2C3D4-0004-0004-0004-000000000004}" EndProject +Project("{C7167F0D-BC9F-4E6A-9F78-5F2B13F8E92E}") = "WindowSeat.Package", "packaging\WindowSeat.Package\WindowSeat.Package.wapproj", "{D1E2F3A4-0005-0005-0005-000000000005}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -32,5 +34,9 @@ Global {A1B2C3D4-0004-0004-0004-000000000004}.Debug|Any CPU.Build.0 = Debug|Any CPU {A1B2C3D4-0004-0004-0004-000000000004}.Release|Any CPU.ActiveCfg = Release|Any CPU {A1B2C3D4-0004-0004-0004-000000000004}.Release|Any CPU.Build.0 = Release|Any CPU + {D1E2F3A4-0005-0005-0005-000000000005}.Debug|Any CPU.ActiveCfg = Debug|x64 + {D1E2F3A4-0005-0005-0005-000000000005}.Debug|Any CPU.Build.0 = Debug|x64 + {D1E2F3A4-0005-0005-0005-000000000005}.Release|Any CPU.ActiveCfg = Release|x64 + {D1E2F3A4-0005-0005-0005-000000000005}.Release|Any CPU.Build.0 = Release|x64 EndGlobalSection EndGlobal From 6e3395dd3570ed63ec9d9f4029b2daed5e54e631 Mon Sep 17 00:00:00 2001 From: GrumpyGent <2617168+TheGrumpyGent@users.noreply.github.com> Date: Tue, 31 Mar 2026 16:27:10 -0400 Subject: [PATCH 09/14] feat(ci): build MSIX via wapproj and upload as Store submission artifact --- .github/workflows/release.yml | 39 +++++++++++++++++++++++++++++------ 1 file changed, 33 insertions(+), 6 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index f6d319c..9cf8ebc 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -18,6 +18,9 @@ jobs: with: dotnet-version: '10.0.x' + - name: Setup MSBuild + uses: microsoft/setup-msbuild@v2 + - name: Extract version from tag id: version shell: pwsh @@ -52,16 +55,40 @@ jobs: --output publish/ /p:Version=${{ steps.version.outputs.VERSION }} - # ── MSIX packaging ──────────────────────────────────────────────────── - # TODO: Wire up Windows Application Packaging Project (wapproj) here - # once packaging/ scaffold is complete. For v1.0, zip the publish output - # as a stopgap so users can still download and run. - - - name: Package (stopgap zip) + # ── ZIP (GitHub Releases sideload) ──────────────────────────────────── + - name: Package zip shell: pwsh run: | Compress-Archive -Path publish/* -DestinationPath "WindowSeat-${{ steps.version.outputs.VERSION }}-win-x64.zip" + # ── MSIX (Microsoft Store submission) ───────────────────────────────── + - name: Patch manifest version + shell: pwsh + run: | + $v = "${{ steps.version.outputs.VERSION }}.0" + $path = "packaging/WindowSeat.Package/Package.appxmanifest" + (Get-Content $path -Raw) ` + -replace '(?<=]*Version=")[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+', $v | + Set-Content $path + Write-Host "Manifest version set to $v" + + - name: Build MSIX package + shell: pwsh + run: | + msbuild packaging/WindowSeat.Package/WindowSeat.Package.wapproj ` + /p:Configuration=Release ` + /p:Platform=x64 ` + /p:AppxPackageSigningEnabled=false ` + /p:AppxAutoIncrementPackageRevision=false ` + /p:GenerateTestArtifacts=false ` + "/p:AppxPackageDir=${{ github.workspace }}\artifacts\msix\\" + + - name: Upload MSIX (Store submission artifact) + uses: actions/upload-artifact@v4 + with: + name: WindowSeat-${{ steps.version.outputs.VERSION }}-store-package + path: artifacts/msix/*.msix + - name: Create GitHub Release uses: softprops/action-gh-release@v2 with: From 1b20bedf8e01e2a911b81605951a13500b56d619 Mon Sep 17 00:00:00 2001 From: GrumpyGent <2617168+TheGrumpyGent@users.noreply.github.com> Date: Tue, 31 Mar 2026 16:29:58 -0400 Subject: [PATCH 10/14] feat(build): add local MSIX packaging to build.ps1 -Package switch --- scripts/build.ps1 | 35 +++++++++++++++++++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) diff --git a/scripts/build.ps1 b/scripts/build.ps1 index 0fc32ba..563aaab 100644 --- a/scripts/build.ps1 +++ b/scripts/build.ps1 @@ -32,6 +32,8 @@ $RepoRoot = Split-Path $PSScriptRoot -Parent $SolutionFile = Join-Path $RepoRoot 'WindowSeat.sln' $PublishDir = Join-Path $RepoRoot 'publish' $TestProject = Join-Path $RepoRoot 'src/WindowSeat.Tests/WindowSeat.Tests.csproj' +$ArtifactsDir = Join-Path $RepoRoot 'artifacts' +$WaprojFile = Join-Path $RepoRoot 'packaging\WindowSeat.Package\WindowSeat.Package.wapproj' function Write-Step([string]$message) { Write-Host "`n==> $message" -ForegroundColor Cyan @@ -66,10 +68,39 @@ if ($Package) { --output $PublishDir if ($LASTEXITCODE -ne 0) { throw "Publish failed." } - Write-Step "Creating zip" + Write-Step "Creating ZIP" $zipPath = Join-Path $RepoRoot "WindowSeat-local-win-x64.zip" Compress-Archive -Path "$PublishDir/*" -DestinationPath $zipPath -Force - Write-Host "Package created: $zipPath" -ForegroundColor Green + Write-Host "ZIP created: $zipPath" -ForegroundColor Green + + # ── MSIX (requires VS 2022 with Windows application development workload) ── + Write-Step "Building MSIX package" + $vswhere = "${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\vswhere.exe" + if (-not (Test-Path $vswhere)) { + Write-Warning "vswhere.exe not found — skipping MSIX. Install Visual Studio 2022 to enable local MSIX packaging." + } else { + $msbuild = & $vswhere -latest -requires Microsoft.Component.MSBuild ` + -find MSBuild\**\Bin\MSBuild.exe | Select-Object -First 1 + if (-not $msbuild) { + Write-Warning "MSBuild not found via vswhere — skipping MSIX." + } else { + $msixOut = Join-Path $ArtifactsDir 'msix' + if (Test-Path $msixOut) { Remove-Item $msixOut -Recurse -Force } + New-Item -ItemType Directory -Force -Path $msixOut | Out-Null + + & $msbuild $WaprojFile ` + /p:Configuration=$Configuration ` + /p:Platform=x64 ` + /p:AppxPackageSigningEnabled=false ` + /p:AppxAutoIncrementPackageRevision=false ` + /p:GenerateTestArtifacts=false ` + "/p:AppxPackageDir=$msixOut\\" + if ($LASTEXITCODE -ne 0) { throw "MSIX build failed." } + + $msix = Get-ChildItem $msixOut -Filter *.msix | Select-Object -First 1 + Write-Host "MSIX created: $($msix.FullName)" -ForegroundColor Green + } + } } Write-Host "`nBuild complete." -ForegroundColor Green From d70420e0179d0a8a0c6e3f4224428dea2416ce2e Mon Sep 17 00:00:00 2001 From: GrumpyGent <2617168+TheGrumpyGent@users.noreply.github.com> Date: Tue, 31 Mar 2026 16:31:54 -0400 Subject: [PATCH 11/14] docs: update installation and uninstall instructions for Store/ZIP model --- docs/user-guide.md | 35 ++++++++++++++++++++++++++++------- 1 file changed, 28 insertions(+), 7 deletions(-) diff --git a/docs/user-guide.md b/docs/user-guide.md index dbe8966..5e9eaad 100644 --- a/docs/user-guide.md +++ b/docs/user-guide.md @@ -20,13 +20,26 @@ When you open an application on a multi-monitor Windows setup, Windows often ign ## Installation +### Microsoft Store (Recommended) + +1. Open the **Microsoft Store** on your PC +2. Search for **WindowSeat** or follow the link from the [GitHub Releases page](https://github.com/stmba/WindowSeat/releases) +3. Click **Get** (or **Install**) +4. WindowSeat starts automatically — look for the 🪑 icon in your system tray + +The Store version updates automatically and can be uninstalled cleanly from Settings → Apps. + +### ZIP Download (Technical Users / Sideload) + +If you prefer not to use the Store: + 1. Go to the [Releases page](https://github.com/stmba/WindowSeat/releases) on GitHub -2. Download the latest `WindowSeat.msix` file -3. Double-click the downloaded file -4. If Windows SmartScreen appears, click **More info** then **Run anyway** - _(This warning appears because WindowSeat is not yet signed with a paid certificate. The source code is fully open for review at github.com/stmba/WindowSeat.)_ -5. Follow the installer prompts and click **Install** -6. WindowSeat starts automatically — look for the 🪑 icon in your system tray +2. Download the latest `WindowSeat-win-x64.zip` +3. Extract the ZIP to a folder of your choice (e.g. `C:\Program Files\WindowSeat\`) +4. Run `WindowSeat.exe` +5. Look for the 🪑 icon in your system tray + +> **Note:** The ZIP version has no installer. There is no entry in Add/Remove Programs. Auto-start at login works via the in-app Settings toggle as normal. --- @@ -91,11 +104,19 @@ Click **Save** to apply your changes. Click **Cancel** to discard them. ## Uninstalling +### Store version + 1. Open **Settings → Apps → Installed apps** 2. Search for **WindowSeat** 3. Click the three-dot menu and choose **Uninstall** -WindowSeat's settings file at `%AppData%\WindowSeat\settings.json` is not removed by the uninstaller. You can delete this folder manually if you want a complete clean removal. +### ZIP version + +1. Right-click the 🪑 tray icon and choose **Exit** +2. Delete the folder you extracted WindowSeat into +3. If you enabled auto-start, open **Task Manager → Startup apps** and remove the WindowSeat entry (or toggle it off in Settings before exiting) + +In both cases, WindowSeat's settings file at `%AppData%\WindowSeat\settings.json` is not removed automatically. Delete that folder manually if you want a complete clean removal. --- From edcc890608c04cbba26e5bdeb112eee85a34fbf0 Mon Sep 17 00:00:00 2001 From: GrumpyGent <2617168+TheGrumpyGent@users.noreply.github.com> Date: Tue, 31 Mar 2026 16:38:41 -0400 Subject: [PATCH 12/14] fix(ci): target app project directly to avoid dotnet CLI failing on wapproj Co-Authored-By: Claude Sonnet 4.6 --- .github/workflows/build.yml | 4 ++-- .github/workflows/release.yml | 4 ++-- packaging/WindowSeat.Package/WindowSeat.Package.wapproj | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 4d408a1..d5190bb 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -20,10 +20,10 @@ jobs: dotnet-version: '10.0.x' - name: Restore dependencies - run: dotnet restore WindowSeat.sln + run: dotnet restore src/WindowSeat.App/WindowSeat.App.csproj src/WindowSeat.Tests/WindowSeat.Tests.csproj - name: Build (Release) - run: dotnet build WindowSeat.sln --configuration Release --no-restore + run: dotnet build src/WindowSeat.App/WindowSeat.App.csproj --configuration Release --no-restore - name: Run tests run: dotnet test src/WindowSeat.Tests/WindowSeat.Tests.csproj --configuration Release --no-build --verbosity normal --logger trx --results-directory TestResults diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 9cf8ebc..2c43994 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -30,11 +30,11 @@ jobs: echo "VERSION=$version" >> $env:GITHUB_OUTPUT - name: Restore dependencies - run: dotnet restore WindowSeat.sln + run: dotnet restore src/WindowSeat.App/WindowSeat.App.csproj src/WindowSeat.Tests/WindowSeat.Tests.csproj - name: Build (Release) run: > - dotnet build WindowSeat.sln + dotnet build src/WindowSeat.App/WindowSeat.App.csproj --configuration Release --no-restore /p:Version=${{ steps.version.outputs.VERSION }} diff --git a/packaging/WindowSeat.Package/WindowSeat.Package.wapproj b/packaging/WindowSeat.Package/WindowSeat.Package.wapproj index 096032e..8fee242 100644 --- a/packaging/WindowSeat.Package/WindowSeat.Package.wapproj +++ b/packaging/WindowSeat.Package/WindowSeat.Package.wapproj @@ -7,7 +7,7 @@ Debug - x86 + x64 {D1E2F3A4-0005-0005-0005-000000000005} {C7167F0D-BC9F-4E6A-9F78-5F2B13F8E92E};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} Package From 0cbb3a73e294b3199810d5e379601ea70b253904 Mon Sep 17 00:00:00 2001 From: GrumpyGent <2617168+TheGrumpyGent@users.noreply.github.com> Date: Tue, 31 Mar 2026 16:39:39 -0400 Subject: [PATCH 13/14] fix(ci): split dotnet restore into per-project calls (.NET 10 limitation) --- .github/workflows/build.yml | 4 +++- .github/workflows/release.yml | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index d5190bb..2741e5c 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -20,7 +20,9 @@ jobs: dotnet-version: '10.0.x' - name: Restore dependencies - run: dotnet restore src/WindowSeat.App/WindowSeat.App.csproj src/WindowSeat.Tests/WindowSeat.Tests.csproj + run: | + dotnet restore src/WindowSeat.App/WindowSeat.App.csproj + dotnet restore src/WindowSeat.Tests/WindowSeat.Tests.csproj - name: Build (Release) run: dotnet build src/WindowSeat.App/WindowSeat.App.csproj --configuration Release --no-restore diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 2c43994..b0e8e76 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -30,7 +30,9 @@ jobs: echo "VERSION=$version" >> $env:GITHUB_OUTPUT - name: Restore dependencies - run: dotnet restore src/WindowSeat.App/WindowSeat.App.csproj src/WindowSeat.Tests/WindowSeat.Tests.csproj + run: | + dotnet restore src/WindowSeat.App/WindowSeat.App.csproj + dotnet restore src/WindowSeat.Tests/WindowSeat.Tests.csproj - name: Build (Release) run: > From b7dcfacbc368a78040b9aa0f6ac8247d28dc3625 Mon Sep 17 00:00:00 2001 From: GrumpyGent <2617168+TheGrumpyGent@users.noreply.github.com> Date: Tue, 31 Mar 2026 19:54:02 -0400 Subject: [PATCH 14/14] fix(ci): build test project before running tests The build step only compiled the app project, leaving the test DLL absent. --no-build on dotnet test then failed to find it. Co-Authored-By: Claude Sonnet 4.6 --- .github/workflows/build.yml | 4 +++- .github/workflows/release.yml | 8 +++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 2741e5c..4336ce9 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -25,7 +25,9 @@ jobs: dotnet restore src/WindowSeat.Tests/WindowSeat.Tests.csproj - name: Build (Release) - run: dotnet build src/WindowSeat.App/WindowSeat.App.csproj --configuration Release --no-restore + run: | + dotnet build src/WindowSeat.App/WindowSeat.App.csproj --configuration Release --no-restore + dotnet build src/WindowSeat.Tests/WindowSeat.Tests.csproj --configuration Release --no-restore - name: Run tests run: dotnet test src/WindowSeat.Tests/WindowSeat.Tests.csproj --configuration Release --no-build --verbosity normal --logger trx --results-directory TestResults diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index b0e8e76..3d0f49c 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -35,11 +35,9 @@ jobs: dotnet restore src/WindowSeat.Tests/WindowSeat.Tests.csproj - name: Build (Release) - run: > - dotnet build src/WindowSeat.App/WindowSeat.App.csproj - --configuration Release - --no-restore - /p:Version=${{ steps.version.outputs.VERSION }} + run: | + dotnet build src/WindowSeat.App/WindowSeat.App.csproj --configuration Release --no-restore /p:Version=${{ steps.version.outputs.VERSION }} + dotnet build src/WindowSeat.Tests/WindowSeat.Tests.csproj --configuration Release --no-restore - name: Run tests run: >