diff --git a/README.md b/README.md index 36da275..78130f6 100644 --- a/README.md +++ b/README.md @@ -252,9 +252,10 @@ the exact tag and submits its unsigned `.msixupload` to Store product `9N0JG96LRC4W` through Microsoft's Store Developer CLI; Partner Center signs the accepted package, and production Store signing is complete. Store installs check Microsoft's native package-update API on launch, notify when an update is -available, and hand installation back to the Store. The standalone GitHub MSI -remains unsigned; the certificate-backed offline-WebView2 MSI workflow is only -a fallback. +available, and hand installation back to the Store. DPI-tailored unplated icon +assets keep the Store taskbar and Start icon as sharp and background-free as +the direct MSI. The standalone GitHub MSI remains unsigned; the +certificate-backed offline-WebView2 MSI workflow is only a fallback. Listing copy, privacy and user-content policies, in-product inappropriate- content reporting, and release credentials are configured. The first automated submission was accepted by Partner Center on 2026-07-28; Store signing was diff --git a/ROADMAP.md b/ROADMAP.md index 77a062e..7a1f290 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -2459,6 +2459,12 @@ and a keyboard-reachable **Open Microsoft Store** handoff while Microsoft Store retains ownership of download and installation. Direct installs keep using the signed GitHub Releases updater. +**DAN-36 Microsoft Store icon parity shipped (2026-07-30):** The MSIX now +includes exact-size dark- and light-shell unplated app-list resources generated +from Strand's canonical icon. Windows can select a pixel-matched taskbar or +Start asset at every supported display scale instead of shrinking the plated +fallback onto an accent-color backplate. + **DAN-38 newest-first PR timeline shipped (2026-07-30):** The provider-neutral pull-request chronology now places the latest commit, comment, or lifecycle event at the top while retaining deterministic tie ordering. Focused frontend diff --git a/TASKS.md b/TASKS.md index 859f3b9..b6ec568 100644 --- a/TASKS.md +++ b/TASKS.md @@ -2050,6 +2050,9 @@ quick-wins from that audit already landed (see ROADMAP changelog). - ☑ Notify Microsoft Store installs when an update is available (`microsoft_store_update_available`, launch toast, and Settings check / **Open Microsoft Store** handoff; DAN-35, 2026-07-30). +- ☑ Match Microsoft Store icon rendering to the direct MSI (exact target-size + `unplated` and `lightunplated` `Square44x44Logo` assets plus MSIX policy + coverage; DAN-36, 2026-07-30). - ☑ Automate GitHub release submission to Microsoft Store (release-published trigger, production identity, official Store Developer CLI action, protected environment credential contract, and manual build-only recovery in diff --git a/crates/strand-tauri/icons/README.md b/crates/strand-tauri/icons/README.md index 063513f..f35ff3a 100644 --- a/crates/strand-tauri/icons/README.md +++ b/crates/strand-tauri/icons/README.md @@ -11,3 +11,12 @@ root with: On macOS/Linux, run the equivalent local binary without the `.cmd` suffix. This populates the PNG, ICO, ICNS, Microsoft Store, Android, and iOS variants in this directory. + +Then regenerate the MSIX app-list variants on Windows: + +``` +.\scripts\generate-msix-icons.ps1 +``` + +These exact-size dark- and light-shell resources prevent Windows from scaling +the plated fallback behind the Store app icon. diff --git a/crates/strand-tauri/icons/Square44x44Logo.targetsize-16_altform-lightunplated.png b/crates/strand-tauri/icons/Square44x44Logo.targetsize-16_altform-lightunplated.png new file mode 100644 index 0000000..5052493 Binary files /dev/null and b/crates/strand-tauri/icons/Square44x44Logo.targetsize-16_altform-lightunplated.png differ diff --git a/crates/strand-tauri/icons/Square44x44Logo.targetsize-16_altform-unplated.png b/crates/strand-tauri/icons/Square44x44Logo.targetsize-16_altform-unplated.png new file mode 100644 index 0000000..5052493 Binary files /dev/null and b/crates/strand-tauri/icons/Square44x44Logo.targetsize-16_altform-unplated.png differ diff --git a/crates/strand-tauri/icons/Square44x44Logo.targetsize-20_altform-lightunplated.png b/crates/strand-tauri/icons/Square44x44Logo.targetsize-20_altform-lightunplated.png new file mode 100644 index 0000000..3e11276 Binary files /dev/null and b/crates/strand-tauri/icons/Square44x44Logo.targetsize-20_altform-lightunplated.png differ diff --git a/crates/strand-tauri/icons/Square44x44Logo.targetsize-20_altform-unplated.png b/crates/strand-tauri/icons/Square44x44Logo.targetsize-20_altform-unplated.png new file mode 100644 index 0000000..3e11276 Binary files /dev/null and b/crates/strand-tauri/icons/Square44x44Logo.targetsize-20_altform-unplated.png differ diff --git a/crates/strand-tauri/icons/Square44x44Logo.targetsize-24_altform-lightunplated.png b/crates/strand-tauri/icons/Square44x44Logo.targetsize-24_altform-lightunplated.png new file mode 100644 index 0000000..33fe365 Binary files /dev/null and b/crates/strand-tauri/icons/Square44x44Logo.targetsize-24_altform-lightunplated.png differ diff --git a/crates/strand-tauri/icons/Square44x44Logo.targetsize-24_altform-unplated.png b/crates/strand-tauri/icons/Square44x44Logo.targetsize-24_altform-unplated.png new file mode 100644 index 0000000..33fe365 Binary files /dev/null and b/crates/strand-tauri/icons/Square44x44Logo.targetsize-24_altform-unplated.png differ diff --git a/crates/strand-tauri/icons/Square44x44Logo.targetsize-256_altform-lightunplated.png b/crates/strand-tauri/icons/Square44x44Logo.targetsize-256_altform-lightunplated.png new file mode 100644 index 0000000..87611d3 Binary files /dev/null and b/crates/strand-tauri/icons/Square44x44Logo.targetsize-256_altform-lightunplated.png differ diff --git a/crates/strand-tauri/icons/Square44x44Logo.targetsize-256_altform-unplated.png b/crates/strand-tauri/icons/Square44x44Logo.targetsize-256_altform-unplated.png new file mode 100644 index 0000000..87611d3 Binary files /dev/null and b/crates/strand-tauri/icons/Square44x44Logo.targetsize-256_altform-unplated.png differ diff --git a/crates/strand-tauri/icons/Square44x44Logo.targetsize-30_altform-lightunplated.png b/crates/strand-tauri/icons/Square44x44Logo.targetsize-30_altform-lightunplated.png new file mode 100644 index 0000000..55d59d0 Binary files /dev/null and b/crates/strand-tauri/icons/Square44x44Logo.targetsize-30_altform-lightunplated.png differ diff --git a/crates/strand-tauri/icons/Square44x44Logo.targetsize-30_altform-unplated.png b/crates/strand-tauri/icons/Square44x44Logo.targetsize-30_altform-unplated.png new file mode 100644 index 0000000..55d59d0 Binary files /dev/null and b/crates/strand-tauri/icons/Square44x44Logo.targetsize-30_altform-unplated.png differ diff --git a/crates/strand-tauri/icons/Square44x44Logo.targetsize-32_altform-lightunplated.png b/crates/strand-tauri/icons/Square44x44Logo.targetsize-32_altform-lightunplated.png new file mode 100644 index 0000000..67dd498 Binary files /dev/null and b/crates/strand-tauri/icons/Square44x44Logo.targetsize-32_altform-lightunplated.png differ diff --git a/crates/strand-tauri/icons/Square44x44Logo.targetsize-32_altform-unplated.png b/crates/strand-tauri/icons/Square44x44Logo.targetsize-32_altform-unplated.png new file mode 100644 index 0000000..67dd498 Binary files /dev/null and b/crates/strand-tauri/icons/Square44x44Logo.targetsize-32_altform-unplated.png differ diff --git a/crates/strand-tauri/icons/Square44x44Logo.targetsize-36_altform-lightunplated.png b/crates/strand-tauri/icons/Square44x44Logo.targetsize-36_altform-lightunplated.png new file mode 100644 index 0000000..233af57 Binary files /dev/null and b/crates/strand-tauri/icons/Square44x44Logo.targetsize-36_altform-lightunplated.png differ diff --git a/crates/strand-tauri/icons/Square44x44Logo.targetsize-36_altform-unplated.png b/crates/strand-tauri/icons/Square44x44Logo.targetsize-36_altform-unplated.png new file mode 100644 index 0000000..233af57 Binary files /dev/null and b/crates/strand-tauri/icons/Square44x44Logo.targetsize-36_altform-unplated.png differ diff --git a/crates/strand-tauri/icons/Square44x44Logo.targetsize-40_altform-lightunplated.png b/crates/strand-tauri/icons/Square44x44Logo.targetsize-40_altform-lightunplated.png new file mode 100644 index 0000000..daf47f5 Binary files /dev/null and b/crates/strand-tauri/icons/Square44x44Logo.targetsize-40_altform-lightunplated.png differ diff --git a/crates/strand-tauri/icons/Square44x44Logo.targetsize-40_altform-unplated.png b/crates/strand-tauri/icons/Square44x44Logo.targetsize-40_altform-unplated.png new file mode 100644 index 0000000..daf47f5 Binary files /dev/null and b/crates/strand-tauri/icons/Square44x44Logo.targetsize-40_altform-unplated.png differ diff --git a/crates/strand-tauri/icons/Square44x44Logo.targetsize-44_altform-lightunplated.png b/crates/strand-tauri/icons/Square44x44Logo.targetsize-44_altform-lightunplated.png new file mode 100644 index 0000000..227e89e Binary files /dev/null and b/crates/strand-tauri/icons/Square44x44Logo.targetsize-44_altform-lightunplated.png differ diff --git a/crates/strand-tauri/icons/Square44x44Logo.targetsize-44_altform-unplated.png b/crates/strand-tauri/icons/Square44x44Logo.targetsize-44_altform-unplated.png new file mode 100644 index 0000000..227e89e Binary files /dev/null and b/crates/strand-tauri/icons/Square44x44Logo.targetsize-44_altform-unplated.png differ diff --git a/crates/strand-tauri/icons/Square44x44Logo.targetsize-48_altform-lightunplated.png b/crates/strand-tauri/icons/Square44x44Logo.targetsize-48_altform-lightunplated.png new file mode 100644 index 0000000..dfc1bd9 Binary files /dev/null and b/crates/strand-tauri/icons/Square44x44Logo.targetsize-48_altform-lightunplated.png differ diff --git a/crates/strand-tauri/icons/Square44x44Logo.targetsize-48_altform-unplated.png b/crates/strand-tauri/icons/Square44x44Logo.targetsize-48_altform-unplated.png new file mode 100644 index 0000000..dfc1bd9 Binary files /dev/null and b/crates/strand-tauri/icons/Square44x44Logo.targetsize-48_altform-unplated.png differ diff --git a/crates/strand-tauri/icons/Square44x44Logo.targetsize-60_altform-lightunplated.png b/crates/strand-tauri/icons/Square44x44Logo.targetsize-60_altform-lightunplated.png new file mode 100644 index 0000000..0829973 Binary files /dev/null and b/crates/strand-tauri/icons/Square44x44Logo.targetsize-60_altform-lightunplated.png differ diff --git a/crates/strand-tauri/icons/Square44x44Logo.targetsize-60_altform-unplated.png b/crates/strand-tauri/icons/Square44x44Logo.targetsize-60_altform-unplated.png new file mode 100644 index 0000000..0829973 Binary files /dev/null and b/crates/strand-tauri/icons/Square44x44Logo.targetsize-60_altform-unplated.png differ diff --git a/crates/strand-tauri/icons/Square44x44Logo.targetsize-64_altform-lightunplated.png b/crates/strand-tauri/icons/Square44x44Logo.targetsize-64_altform-lightunplated.png new file mode 100644 index 0000000..c007615 Binary files /dev/null and b/crates/strand-tauri/icons/Square44x44Logo.targetsize-64_altform-lightunplated.png differ diff --git a/crates/strand-tauri/icons/Square44x44Logo.targetsize-64_altform-unplated.png b/crates/strand-tauri/icons/Square44x44Logo.targetsize-64_altform-unplated.png new file mode 100644 index 0000000..c007615 Binary files /dev/null and b/crates/strand-tauri/icons/Square44x44Logo.targetsize-64_altform-unplated.png differ diff --git a/crates/strand-tauri/icons/Square44x44Logo.targetsize-72_altform-lightunplated.png b/crates/strand-tauri/icons/Square44x44Logo.targetsize-72_altform-lightunplated.png new file mode 100644 index 0000000..f1ccf2b Binary files /dev/null and b/crates/strand-tauri/icons/Square44x44Logo.targetsize-72_altform-lightunplated.png differ diff --git a/crates/strand-tauri/icons/Square44x44Logo.targetsize-72_altform-unplated.png b/crates/strand-tauri/icons/Square44x44Logo.targetsize-72_altform-unplated.png new file mode 100644 index 0000000..f1ccf2b Binary files /dev/null and b/crates/strand-tauri/icons/Square44x44Logo.targetsize-72_altform-unplated.png differ diff --git a/crates/strand-tauri/icons/Square44x44Logo.targetsize-80_altform-lightunplated.png b/crates/strand-tauri/icons/Square44x44Logo.targetsize-80_altform-lightunplated.png new file mode 100644 index 0000000..2b0f7e5 Binary files /dev/null and b/crates/strand-tauri/icons/Square44x44Logo.targetsize-80_altform-lightunplated.png differ diff --git a/crates/strand-tauri/icons/Square44x44Logo.targetsize-80_altform-unplated.png b/crates/strand-tauri/icons/Square44x44Logo.targetsize-80_altform-unplated.png new file mode 100644 index 0000000..2b0f7e5 Binary files /dev/null and b/crates/strand-tauri/icons/Square44x44Logo.targetsize-80_altform-unplated.png differ diff --git a/crates/strand-tauri/icons/Square44x44Logo.targetsize-96_altform-lightunplated.png b/crates/strand-tauri/icons/Square44x44Logo.targetsize-96_altform-lightunplated.png new file mode 100644 index 0000000..fd45f12 Binary files /dev/null and b/crates/strand-tauri/icons/Square44x44Logo.targetsize-96_altform-lightunplated.png differ diff --git a/crates/strand-tauri/icons/Square44x44Logo.targetsize-96_altform-unplated.png b/crates/strand-tauri/icons/Square44x44Logo.targetsize-96_altform-unplated.png new file mode 100644 index 0000000..fd45f12 Binary files /dev/null and b/crates/strand-tauri/icons/Square44x44Logo.targetsize-96_altform-unplated.png differ diff --git a/docs/learnings.md b/docs/learnings.md index 9fab977..bb911ad 100644 --- a/docs/learnings.md +++ b/docs/learnings.md @@ -2125,3 +2125,12 @@ Store product page, but Microsoft Store remains responsible for downloading, signing, and installing the package. The API requires package identity, so browser and unpackaged development runs can verify the UI/state contract but not a real availability response. + +**MSIX shell icons need the complete target-size unplated matrix +(2026-07-30).** A lone `Square44x44Logo.targetsize-44_altform-unplated.png` +does not cover Windows display scales. The shell then shrinks the plated +manifest logo, producing blur and an accent-color backplate. Generate exact +16/20/24/30/32/36/40/44/48/60/64/72/80/96/256 px `unplated` and +`lightunplated` variants directly from canonical `strand.png`, copy every +variant into the MSIX, and keep the cross-platform policy check aligned with +that matrix. diff --git a/docs/microsoft-store-submission.md b/docs/microsoft-store-submission.md index a42e93c..c2e9eb7 100644 --- a/docs/microsoft-store-submission.md +++ b/docs/microsoft-store-submission.md @@ -7,6 +7,11 @@ release executable, manifest, and Store assets with Microsoft's MakeAppx tool. The older MSI/EXE workflow remains available as a fallback, but it requires an external CA-backed Windows code-signing certificate. +The `Square44x44Logo` resource includes exact target-size `unplated` and +`lightunplated` variants generated from the canonical `strand.png`. Keep the +full size matrix when changing the icon; otherwise Windows can resample the +plated fallback and add an accent-color backplate on the taskbar or Start. + ## Production identity Strand is registered as Store product `9N0JG96LRC4W`. Its public manifest diff --git a/docs/packaging.md b/docs/packaging.md index 07f5dfd..e1df085 100644 --- a/docs/packaging.md +++ b/docs/packaging.md @@ -117,7 +117,10 @@ The preferred Microsoft Store route is the packaged-classic MSIX assembled by the production Partner Center identity, retains the unsigned `.msixupload` as a private workflow artifact, and submits it to Store product `9N0JG96LRC4W` through Microsoft's official Store Developer CLI action. The Store signs the -accepted package during certification. +accepted package during certification. The package carries exact target-size +`unplated` and `lightunplated` app-list assets so taskbar and Start use the +same sharp, transparent icon presentation as the direct MSI at every supported +Windows display scale. The workflow requires four encrypted secrets on the `microsoft-store-production` GitHub environment: diff --git a/scripts/build-msix.ps1 b/scripts/build-msix.ps1 index 070bb87..96dc303 100644 --- a/scripts/build-msix.ps1 +++ b/scripts/build-msix.ps1 @@ -106,9 +106,19 @@ foreach ($asset in @('StoreLogo.png', 'Square150x150Logo.png', 'Square44x44Logo. -LiteralPath (Join-Path $repoRoot "crates\strand-tauri\icons\$asset") ` -Destination (Join-Path $layoutPath "Assets\$asset") } -Copy-Item ` - -LiteralPath (Join-Path $repoRoot 'crates\strand-tauri\icons\Square44x44Logo.png') ` - -Destination (Join-Path $layoutPath 'Assets\Square44x44Logo.targetsize-44_altform-unplated.png') +$targetSizes = @( + 16, 20, 24, 30, 32, 36, 40, 44, + 48, 60, 64, 72, 80, 96, 256 +) +foreach ($targetSize in $targetSizes) { + foreach ($alternateForm in @('unplated', 'lightunplated')) { + $asset = + "Square44x44Logo.targetsize-$($targetSize)_altform-$alternateForm.png" + Copy-Item ` + -LiteralPath (Join-Path $repoRoot "crates\strand-tauri\icons\$asset") ` + -Destination (Join-Path $layoutPath "Assets\$asset") + } +} $templatePath = Join-Path $repoRoot 'packaging\msix\AppxManifest.xml.in' $manifest = Get-Content -Raw -LiteralPath $templatePath diff --git a/scripts/check-msix.mjs b/scripts/check-msix.mjs index 278ee8d..193e643 100644 --- a/scripts/check-msix.mjs +++ b/scripts/check-msix.mjs @@ -3,6 +3,7 @@ import { readFileSync } from 'node:fs'; const packageJson = JSON.parse(readFileSync('package.json', 'utf8')); const manifest = readFileSync('packaging/msix/AppxManifest.xml.in', 'utf8'); const buildScript = readFileSync('scripts/build-msix.ps1', 'utf8'); +const iconScript = readFileSync('scripts/generate-msix-icons.ps1', 'utf8'); const workflow = readFileSync( '.github/workflows/microsoft-store-msix.yml', 'utf8', @@ -66,6 +67,42 @@ for (const fragment of [ } } +const targetSizes = [ + 16, 20, 24, 30, 32, 36, 40, 44, + 48, 60, 64, 72, 80, 96, 256, +]; +for (const targetSize of targetSizes) { + for (const alternateForm of ['unplated', 'lightunplated']) { + const path = 'crates/strand-tauri/icons/' + + `Square44x44Logo.targetsize-${targetSize}_altform-${alternateForm}.png`; + const size = pngSize(path); + if (size.width !== targetSize || size.height !== targetSize) { + fail(`${path} must be ${targetSize}x${targetSize}, ` + + `got ${size.width}x${size.height}`); + } + } +} + +for (const fragment of [ + 'HighQualityBicubic', + "('unplated', 'lightunplated')", + '16, 20, 24, 30, 32, 36, 40, 44, 48, 60, 64, 72, 80, 96, 256', + 'Square44x44Logo.targetsize-$($targetSize)_altform-$alternateForm.png', +]) { + const normalizedFragment = fragment.replace(/\s+/g, ' '); + const normalizedIconScript = iconScript.replace(/\s+/g, ' '); + const normalizedBuildScript = buildScript.replace(/\s+/g, ' '); + if (!normalizedIconScript.includes(normalizedFragment)) { + fail(`MSIX icon generator must retain ${JSON.stringify(fragment)}`); + } + if ( + fragment !== 'HighQualityBicubic' + && !normalizedBuildScript.includes(normalizedFragment) + ) { + fail(`MSIX build must retain ${JSON.stringify(fragment)}`); + } +} + if (!updatesStore.includes('UPDATES_MANAGED_BY_STORE')) { fail('update store must recognize Store-managed MSIX updates'); } @@ -119,6 +156,7 @@ if (!/^\d+\.\d+\.\d+$/.test(packageJson.version)) { console.log( `MSIX packaging policy is valid for Strand ${packageJson.version}: ` + 'x64 packaged-classic full trust, Windows 11 minimum, parameterized ' - + 'Partner Center identity, validated assets, Store-managed updates, and ' + + 'Partner Center identity, DPI-tailored unplated assets, Store-managed ' + + 'updates, and ' + 'GitHub-to-Partner-Center release submission.', ); diff --git a/scripts/generate-msix-icons.ps1 b/scripts/generate-msix-icons.ps1 new file mode 100644 index 0000000..3dc0624 --- /dev/null +++ b/scripts/generate-msix-icons.ps1 @@ -0,0 +1,77 @@ +param( + [string]$OutputDirectory +) + +$ErrorActionPreference = 'Stop' + +Add-Type -AssemblyName System.Drawing + +$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot '..')).Path +$sourcePath = Join-Path $repoRoot 'strand.png' +if (-not $OutputDirectory) { + $OutputDirectory = Join-Path $repoRoot 'crates\strand-tauri\icons' +} +$OutputDirectory = [IO.Path]::GetFullPath($OutputDirectory) +New-Item -ItemType Directory -Force -Path $OutputDirectory | Out-Null + +$targetSizes = @( + 16, 20, 24, 30, 32, 36, 40, 44, + 48, 60, 64, 72, 80, 96, 256 +) +$alternateForms = @('unplated', 'lightunplated') +$source = [Drawing.Image]::FromFile($sourcePath) + +try { + foreach ($targetSize in $targetSizes) { + foreach ($alternateForm in $alternateForms) { + $bitmap = [Drawing.Bitmap]::new( + $targetSize, + $targetSize, + [Drawing.Imaging.PixelFormat]::Format32bppArgb + ) + try { + $graphics = [Drawing.Graphics]::FromImage($bitmap) + try { + $graphics.Clear([Drawing.Color]::Transparent) + $graphics.CompositingMode = + [Drawing.Drawing2D.CompositingMode]::SourceCopy + $graphics.CompositingQuality = + [Drawing.Drawing2D.CompositingQuality]::HighQuality + $graphics.InterpolationMode = + [Drawing.Drawing2D.InterpolationMode]::HighQualityBicubic + $graphics.PixelOffsetMode = + [Drawing.Drawing2D.PixelOffsetMode]::HighQuality + $graphics.SmoothingMode = + [Drawing.Drawing2D.SmoothingMode]::HighQuality + $graphics.DrawImage( + $source, + [Drawing.Rectangle]::new(0, 0, $targetSize, $targetSize), + 0, + 0, + $source.Width, + $source.Height, + [Drawing.GraphicsUnit]::Pixel + ) + } finally { + $graphics.Dispose() + } + + $filename = + "Square44x44Logo.targetsize-$($targetSize)_altform-$alternateForm.png" + $bitmap.Save( + (Join-Path $OutputDirectory $filename), + [Drawing.Imaging.ImageFormat]::Png + ) + } finally { + $bitmap.Dispose() + } + } + } +} finally { + $source.Dispose() +} + +Write-Host ( + "Generated $($targetSizes.Count * $alternateForms.Count) " + + "MSIX app-list icons in $OutputDirectory" +)