Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 6 additions & 0 deletions ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 3 additions & 0 deletions TASKS.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
9 changes: 9 additions & 0 deletions crates/strand-tauri/icons/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
9 changes: 9 additions & 0 deletions docs/learnings.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
5 changes: 5 additions & 0 deletions docs/microsoft-store-submission.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
5 changes: 4 additions & 1 deletion docs/packaging.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
16 changes: 13 additions & 3 deletions scripts/build-msix.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
40 changes: 39 additions & 1 deletion scripts/check-msix.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down Expand Up @@ -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');
}
Expand Down Expand Up @@ -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.',
);
77 changes: 77 additions & 0 deletions scripts/generate-msix-icons.ps1
Original file line number Diff line number Diff line change
@@ -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"
)
Loading